Compare commits
33
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e143d05801
|
||
|
|
7c0ebe6668
|
||
|
|
6eb54a6f11
|
||
|
|
db757de71c
|
||
|
|
71aed9dcec
|
||
|
|
b809542083
|
||
|
|
749b93e3af
|
||
|
|
df3bf89079
|
||
|
|
de346c0c6e
|
||
|
|
6df63373ab
|
||
|
|
a0fe77fe66
|
||
|
|
151112bd2a
|
||
|
|
366abf61b3
|
||
|
|
367e78cbf8
|
||
|
|
c056e0bdfc
|
||
|
|
12890293e0
|
||
|
|
845f1fe262
|
||
|
|
bc9f1a1d1f
|
||
|
|
84df948012
|
||
|
|
1200489186
|
||
|
|
74d0b24f5f
|
||
|
|
cbe6d79ab5
|
||
|
|
72618c6c66
|
||
|
|
b6018a0715
|
||
|
|
35b7db81b2
|
||
|
|
89c7d3b0ea
|
||
|
|
13b62c8c02
|
||
|
|
364d55a4b2
|
||
|
|
e4dc8ace04
|
||
|
|
af8ecc748f
|
||
|
|
6d11be728e
|
||
|
|
453940a5ef
|
||
|
|
d45b790122
|
No files matched your search
@@ -19,3 +19,5 @@ GOOGLE_OAUTH_CLIENT_SECRET=your_google_oauth_client_secret
|
|||||||
GOOGLE_ANALITICS_ID=your_google_analytics_id
|
GOOGLE_ANALITICS_ID=your_google_analytics_id
|
||||||
RECAPTCHA_SITEKEY=your_recaptcha_sitekey
|
RECAPTCHA_SITEKEY=your_recaptcha_sitekey
|
||||||
RECAPTCHA_SECRET=your_recaptcha_secret
|
RECAPTCHA_SECRET=your_recaptcha_secret
|
||||||
|
JWT_RSA_PRIVATE_KEY=jwt-rsa256-private.pem
|
||||||
|
JWT_RSA_PUBLIC_KEY=jwt-rsa256-public.pem
|
||||||
@@ -2,3 +2,4 @@
|
|||||||
installed
|
installed
|
||||||
vendor
|
vendor
|
||||||
node_modules
|
node_modules
|
||||||
|
*.pem
|
||||||
+2
-1
@@ -10,7 +10,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"esoko/soko-web": "0.1"
|
"esoko/soko-web": "0.1",
|
||||||
|
"firebase/php-jwt": "^6.4"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^9.6",
|
"phpunit/phpunit": "^9.6",
|
||||||
|
|||||||
Generated
+64
-1
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "3479948070678fa4e980114fbe8b71b5",
|
"content-hash": "f2dcf297a4a619bc5edfe7b4fac0836e",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "esoko/soko-web",
|
"name": "esoko/soko-web",
|
||||||
@@ -35,6 +35,69 @@
|
|||||||
"description": "Lightweight web framework",
|
"description": "Lightweight web framework",
|
||||||
"time": "2023-04-07T17:32:15+00:00"
|
"time": "2023-04-07T17:32:15+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "firebase/php-jwt",
|
||||||
|
"version": "v6.4.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/firebase/php-jwt.git",
|
||||||
|
"reference": "4dd1e007f22a927ac77da5a3fbb067b42d3bc224"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/4dd1e007f22a927ac77da5a3fbb067b42d3bc224",
|
||||||
|
"reference": "4dd1e007f22a927ac77da5a3fbb067b42d3bc224",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.1||^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"guzzlehttp/guzzle": "^6.5||^7.4",
|
||||||
|
"phpspec/prophecy-phpunit": "^1.1",
|
||||||
|
"phpunit/phpunit": "^7.5||^9.5",
|
||||||
|
"psr/cache": "^1.0||^2.0",
|
||||||
|
"psr/http-client": "^1.0",
|
||||||
|
"psr/http-factory": "^1.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-sodium": "Support EdDSA (Ed25519) signatures",
|
||||||
|
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Firebase\\JWT\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Neuman Vong",
|
||||||
|
"email": "neuman+pear@twilio.com",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Anant Narayanan",
|
||||||
|
"email": "anant@php.net",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
|
||||||
|
"homepage": "https://github.com/firebase/php-jwt",
|
||||||
|
"keywords": [
|
||||||
|
"jwt",
|
||||||
|
"php"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/firebase/php-jwt/issues",
|
||||||
|
"source": "https://github.com/firebase/php-jwt/tree/v6.4.0"
|
||||||
|
},
|
||||||
|
"time": "2023-02-09T21:01:23+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "graham-campbell/result-type",
|
"name": "graham-campbell/result-type",
|
||||||
"version": "v1.1.1",
|
"version": "v1.1.1",
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
CREATE TABLE `oauth_tokens` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`nonce` varchar(255) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
|
||||||
|
`user_id` int(10) unsigned DEFAULT NULL,
|
||||||
|
`code` varchar(255) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
|
||||||
|
`created` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
|
`expires` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `code` (`code`)
|
||||||
|
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE `users`
|
||||||
|
ADD `username` varchar(100) DEFAULT NULL,
|
||||||
|
ADD UNIQUE `username` (`username`);
|
||||||
@@ -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 '';
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE `oauth_tokens`
|
||||||
|
ADD `scope` varchar(255) NOT NULL DEFAULT '',
|
||||||
|
ADD `access_token` varchar(255) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
|
||||||
|
ADD UNIQUE `access_token` (`access_token`);
|
||||||
+2
-2
@@ -29,13 +29,13 @@ if ($match !== null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$authorized) {
|
if (!$authorized) {
|
||||||
Container::$request->session()->set('redirect_after_login', $url);
|
Container::$request->session()->set('redirect_after_login', substr($_SERVER['REQUEST_URI'], strlen('/')));
|
||||||
$response = new Redirect(Container::$routeCollection->getRoute('login')->generateLink(), IRedirect::TEMPORARY);
|
$response = new Redirect(Container::$routeCollection->getRoute('login')->generateLink(), IRedirect::TEMPORARY);
|
||||||
header('Location: ' . $response->getUrl(), true, $response->getHttpCode());
|
header('Location: ' . $response->getUrl(), true, $response->getHttpCode());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($method === 'post' && Container::$request->post('anti_csrf_token') !== Container::$request->session()->get('anti_csrf_token')) {
|
if ($method === 'post' && !in_array($url, $antiCsrfTokenExceptions) && Container::$request->post('anti_csrf_token') !== Container::$request->session()->get('anti_csrf_token')) {
|
||||||
$content = new SokoWeb\Response\JsonContent(['error' => 'no_valid_anti_csrf_token']);
|
$content = new SokoWeb\Response\JsonContent(['error' => 'no_valid_anti_csrf_token']);
|
||||||
header('Content-Type: text/html; charset=UTF-8', true, 403);
|
header('Content-Type: text/html; charset=UTF-8', true, 403);
|
||||||
$content->render();
|
$content->render();
|
||||||
|
|||||||
+12
-7
@@ -158,12 +158,17 @@ var RVR = {
|
|||||||
document.getElementById('cover').style.visibility = 'hidden';
|
document.getElementById('cover').style.visibility = 'hidden';
|
||||||
},
|
},
|
||||||
|
|
||||||
observeInput: function (input, buttonToToggle) {
|
observeInput: function (form, observedInputs) {
|
||||||
if (input.defaultValue !== input.value) {
|
var anyChanged = false;
|
||||||
buttonToToggle.disabled = false;
|
|
||||||
} else {
|
for (var i = 0; i < observedInputs.length; i++) {
|
||||||
buttonToToggle.disabled = true;
|
var input = form.elements[observedInputs[i]];
|
||||||
|
if (input.defaultValue !== input.value) {
|
||||||
|
anyChanged = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form.elements.submit.disabled = !anyChanged;
|
||||||
},
|
},
|
||||||
|
|
||||||
observeInputsInForm: function (form, observedInputs) {
|
observeInputsInForm: function (form, observedInputs) {
|
||||||
@@ -174,12 +179,12 @@ var RVR = {
|
|||||||
case 'INPUT':
|
case 'INPUT':
|
||||||
case 'TEXTAREA':
|
case 'TEXTAREA':
|
||||||
input.oninput = function () {
|
input.oninput = function () {
|
||||||
RVR.observeInput(this, form.elements.submit);
|
RVR.observeInput(form, observedInputs);
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case 'SELECT':
|
case 'SELECT':
|
||||||
input.onchange = function () {
|
input.onchange = function () {
|
||||||
RVR.observeInput(this, form.elements.submit);
|
RVR.observeInput(form, observedInputs);
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ class AddUserCommand extends Command
|
|||||||
|
|
||||||
public function execute(InputInterface $input, OutputInterface $output): int
|
public function execute(InputInterface $input, OutputInterface $output): int
|
||||||
{
|
{
|
||||||
|
if (!filter_var($input->getArgument('email'), FILTER_VALIDATE_EMAIL)) {
|
||||||
|
$output->writeln('<error>Please provide a valid email address.</error>');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
$user = new User();
|
$user = new User();
|
||||||
$user->setEmail($input->getArgument('email'));
|
$user->setEmail($input->getArgument('email'));
|
||||||
$user->setPlainPassword($input->getArgument('password'));
|
$user->setPlainPassword($input->getArgument('password'));
|
||||||
|
|||||||
@@ -27,21 +27,27 @@ class LoginController
|
|||||||
|
|
||||||
private UserPasswordResetterRepository $userPasswordResetterRepository;
|
private UserPasswordResetterRepository $userPasswordResetterRepository;
|
||||||
|
|
||||||
|
private string $redirectUrl;
|
||||||
|
|
||||||
public function __construct(IRequest $request)
|
public function __construct(IRequest $request)
|
||||||
{
|
{
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
$this->pdm = new PersistentDataManager();
|
$this->pdm = new PersistentDataManager();
|
||||||
$this->userRepository = new UserRepository();
|
$this->userRepository = new UserRepository();
|
||||||
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
||||||
|
$this->redirectUrl = $this->request->session()->has('redirect_after_login') ?
|
||||||
|
$this->request->session()->get('redirect_after_login') :
|
||||||
|
\Container::$routeCollection->getRoute('index')->generateLink();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLoginForm()
|
public function getLoginForm()
|
||||||
{
|
{
|
||||||
if ($this->request->user() !== null) {
|
if ($this->request->user() !== null) {
|
||||||
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
$this->deleteRedirectUrl();
|
||||||
|
return new Redirect($this->redirectUrl, IRedirect::TEMPORARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new HtmlContent('login/login');
|
return new HtmlContent('login/login', ['redirectUrl' => '/' . $this->redirectUrl]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getGoogleLoginRedirect(): IRedirect
|
public function getGoogleLoginRedirect(): IRedirect
|
||||||
@@ -65,7 +71,8 @@ class LoginController
|
|||||||
public function getRequestPasswordResetForm()
|
public function getRequestPasswordResetForm()
|
||||||
{
|
{
|
||||||
if ($this->request->user() !== null) {
|
if ($this->request->user() !== null) {
|
||||||
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
$this->deleteRedirectUrl();
|
||||||
|
return new Redirect($this->redirectUrl, IRedirect::TEMPORARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new HtmlContent('login/password_reset_request', ['email' => $this->request->query('email')]);
|
return new HtmlContent('login/password_reset_request', ['email' => $this->request->query('email')]);
|
||||||
@@ -79,7 +86,8 @@ class LoginController
|
|||||||
public function getResetPasswordForm()
|
public function getResetPasswordForm()
|
||||||
{
|
{
|
||||||
if ($this->request->user() !== null) {
|
if ($this->request->user() !== null) {
|
||||||
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
$this->deleteRedirectUrl();
|
||||||
|
return new Redirect($this->redirectUrl, IRedirect::TEMPORARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
$token = $this->request->query('token');
|
$token = $this->request->query('token');
|
||||||
@@ -91,20 +99,21 @@ class LoginController
|
|||||||
|
|
||||||
$user = $this->userRepository->getById($resetter->getUserId());
|
$user = $this->userRepository->getById($resetter->getUserId());
|
||||||
|
|
||||||
return new HtmlContent('login/reset_password', ['success' => true, 'token' => $token, 'email' => $user->getEmail()]);
|
return new HtmlContent('login/reset_password', ['success' => true, 'token' => $token, 'email' => $user->getEmail(), 'redirectUrl' => '/' . $this->redirectUrl]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function login(): IContent
|
public function login(): IContent
|
||||||
{
|
{
|
||||||
if ($this->request->user() !== null) {
|
if ($this->request->user() !== null) {
|
||||||
|
$this->deleteRedirectUrl();
|
||||||
return new JsonContent(['success' => true]);
|
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'))) {
|
if ($user === null || !$user->checkPassword($this->request->post('password'))) {
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'error' => [
|
'error' => [
|
||||||
'errorText' => 'No user found with the given email address or the given password is wrong. You can <a href="/password/requestReset?email=' .
|
'errorText' => 'No user found with the given email address / username or the given password is wrong. You can <a href="/password/requestReset?email=' .
|
||||||
urlencode($this->request->post('email')) . '" title="Request password reset">request password reset</a>!'
|
urlencode($this->request->post('email')) . '" title="Request password reset">request password reset</a>!'
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
@@ -112,23 +121,21 @@ class LoginController
|
|||||||
|
|
||||||
$this->request->setUser($user);
|
$this->request->setUser($user);
|
||||||
|
|
||||||
|
$this->deleteRedirectUrl();
|
||||||
return new JsonContent(['success' => true]);
|
return new JsonContent(['success' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loginWithGoogle()
|
public function loginWithGoogle()
|
||||||
{
|
{
|
||||||
$redirectUrl = $this->request->session()->get('redirect_after_login');
|
$defaultError = 'Authentication with Google failed. Please <a href="/login/google" title="Login with Google">try again</a>!';
|
||||||
if ($redirectUrl === null) {
|
|
||||||
$redirectUrl = \Container::$routeCollection->getRoute('index')->generateLink();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->request->user() !== null) {
|
if ($this->request->user() !== null) {
|
||||||
$this->request->session()->delete('redirect_after_login');
|
$this->deleteRedirectUrl();
|
||||||
return new Redirect($redirectUrl, IRedirect::TEMPORARY);
|
return new Redirect($this->redirectUrl, IRedirect::TEMPORARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->request->query('state') !== $this->request->session()->get('oauth_state')) {
|
if ($this->request->query('state') !== $this->request->session()->get('oauth_state')) {
|
||||||
return new HtmlContent('login/google_login');
|
return new HtmlContent('login/google_login_error', ['error' => $defaultError]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$oAuth = new GoogleOAuth(new Request());
|
$oAuth = new GoogleOAuth(new Request());
|
||||||
@@ -138,33 +145,29 @@ class LoginController
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!isset($tokenData['id_token'])) {
|
if (!isset($tokenData['id_token'])) {
|
||||||
return new HtmlContent('login/google_login');
|
return new HtmlContent('login/google_login_error', ['error' => $defaultError]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$jwtParser = new JwtParser($tokenData['id_token']);
|
$jwtParser = new JwtParser($tokenData['id_token']);
|
||||||
$idToken = $jwtParser->getPayload();
|
$idToken = $jwtParser->getPayload();
|
||||||
|
|
||||||
if ($idToken['nonce'] !== $this->request->session()->get('oauth_nonce')) {
|
if ($idToken['nonce'] !== $this->request->session()->get('oauth_nonce')) {
|
||||||
return new HtmlContent('login/google_login');
|
return new HtmlContent('login/google_login_error', ['error' => $defaultError]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$idToken['email_verified']) {
|
if (!$idToken['email_verified']) {
|
||||||
return new HtmlContent('login/google_login');
|
return new HtmlContent('login/google_login_error', ['error' => $defaultError]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = $this->userRepository->getByGoogleSub($idToken['sub']);
|
$user = $this->userRepository->getByGoogleSub($idToken['sub']);
|
||||||
if ($user === null) {
|
if ($user === null) {
|
||||||
return new JsonContent([
|
return new HtmlContent('login/google_login_error', ['error' => 'No user found for this Google account.']);
|
||||||
'error' => [
|
|
||||||
'errorText' => 'No user found for this Google account.'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->request->setUser($user);
|
$this->request->setUser($user);
|
||||||
|
|
||||||
$this->request->session()->delete('redirect_after_login');
|
$this->deleteRedirectUrl();
|
||||||
return new Redirect($redirectUrl, IRedirect::TEMPORARY);
|
return new Redirect($this->redirectUrl, IRedirect::TEMPORARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function logout(): IRedirect
|
public function logout(): IRedirect
|
||||||
@@ -177,9 +180,10 @@ class LoginController
|
|||||||
public function requestPasswordReset(): IContent
|
public function requestPasswordReset(): IContent
|
||||||
{
|
{
|
||||||
if ($this->request->user() !== null) {
|
if ($this->request->user() !== null) {
|
||||||
|
$this->deleteRedirectUrl();
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'redirect' => [
|
'redirect' => [
|
||||||
'target' => '/' . \Container::$routeCollection->getRoute('home')->generateLink()
|
'target' => '/' . $this->redirectUrl
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -196,11 +200,11 @@ class LoginController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = $this->userRepository->getByEmail($this->request->post('email'));
|
$user = $this->userRepository->getByEmailOrUsername($this->request->post('email'));
|
||||||
if ($user === null) {
|
if ($user === null) {
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'error' => [
|
'error' => [
|
||||||
'errorText' => 'No user found with the given email address.'
|
'errorText' => 'No user found with the given email address / username.'
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -240,9 +244,10 @@ class LoginController
|
|||||||
public function resetPassword(): IContent
|
public function resetPassword(): IContent
|
||||||
{
|
{
|
||||||
if ($this->request->user() !== null) {
|
if ($this->request->user() !== null) {
|
||||||
|
$this->deleteRedirectUrl();
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'redirect' => [
|
'redirect' => [
|
||||||
'target' => '/' . \Container::$routeCollection->getRoute('home')->generateLink()
|
'target' => '/' . $this->redirectUrl
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -283,6 +288,7 @@ class LoginController
|
|||||||
|
|
||||||
$this->request->setUser($user);
|
$this->request->setUser($user);
|
||||||
|
|
||||||
|
$this->deleteRedirectUrl();
|
||||||
return new JsonContent(['success' => true]);
|
return new JsonContent(['success' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,4 +305,9 @@ class LoginController
|
|||||||
]);
|
]);
|
||||||
$mail->send();
|
$mail->send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function deleteRedirectUrl(): void
|
||||||
|
{
|
||||||
|
$this->request->session()->delete('redirect_after_login');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<?php namespace RVR\Controller;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use RVR\PersistentData\Model\OAuthToken;
|
||||||
|
use RVR\PersistentData\Model\User;
|
||||||
|
use SokoWeb\Interfaces\Authorization\ISecured;
|
||||||
|
use SokoWeb\Interfaces\Request\IRequest;
|
||||||
|
use SokoWeb\Interfaces\Response\IRedirect;
|
||||||
|
use SokoWeb\Response\Redirect;
|
||||||
|
use SokoWeb\PersistentData\PersistentDataManager;
|
||||||
|
use SokoWeb\Response\HtmlContent;
|
||||||
|
|
||||||
|
class OAuthAuthController implements ISecured
|
||||||
|
{
|
||||||
|
private IRequest $request;
|
||||||
|
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
|
public function __construct(IRequest $request)
|
||||||
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return $this->request->user() !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function auth()
|
||||||
|
{
|
||||||
|
$redirectUri = $this->request->query('redirect_uri');
|
||||||
|
$scope = $this->request->query('scope') ? $this->request->query('scope'): '';
|
||||||
|
$state = $this->request->query('state');
|
||||||
|
$nonce = $this->request->query('nonce') ? $this->request->query('nonce'): '';
|
||||||
|
|
||||||
|
if (!$redirectUri || !$state) {
|
||||||
|
return new HtmlContent('oauth/oauth_error', ['error' => 'An invalid request was made. Please start authentication again.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->request->session()->delete('oauth_payload');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ?User $user
|
||||||
|
*/
|
||||||
|
$user = $this->request->user();
|
||||||
|
$code = bin2hex(random_bytes(16));
|
||||||
|
$accessToken = bin2hex(random_bytes(16));
|
||||||
|
|
||||||
|
$token = new OAuthToken();
|
||||||
|
$token->setNonce($nonce);
|
||||||
|
$token->setScope($scope);
|
||||||
|
$token->setUser($user);
|
||||||
|
$token->setCode($code);
|
||||||
|
$token->setAccessToken($accessToken);
|
||||||
|
$token->setCreatedDate(new DateTime());
|
||||||
|
$token->setExpiresDate(new DateTime('+5 minutes'));
|
||||||
|
$this->pdm->saveToDb($token);
|
||||||
|
|
||||||
|
$redirectUri = $redirectUri;
|
||||||
|
$additionalUriParams = [
|
||||||
|
'state' => $state,
|
||||||
|
'code' => $code
|
||||||
|
];
|
||||||
|
$and = (strpos($redirectUri, '?') !== false) ? '&' : '?';
|
||||||
|
$finalRedirectUri = $redirectUri . $and . http_build_query($additionalUriParams);
|
||||||
|
|
||||||
|
return new Redirect($finalRedirectUri, IRedirect::TEMPORARY);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
<?php namespace RVR\Controller;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use Firebase\JWT\JWT;
|
||||||
|
use RVR\Repository\OAuthTokenRepository;
|
||||||
|
use RVR\Repository\UserRepository;
|
||||||
|
use RVR\PersistentData\Model\User;
|
||||||
|
use SokoWeb\Interfaces\Request\IRequest;
|
||||||
|
use SokoWeb\Interfaces\Response\IContent;
|
||||||
|
use SokoWeb\Response\JsonContent;
|
||||||
|
|
||||||
|
class OAuthController
|
||||||
|
{
|
||||||
|
private IRequest $request;
|
||||||
|
|
||||||
|
private OAuthTokenRepository $oAuthTokenRepository;
|
||||||
|
|
||||||
|
private UserRepository $userRepository;
|
||||||
|
|
||||||
|
public function __construct(IRequest $request)
|
||||||
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
$this->oAuthTokenRepository = new OAuthTokenRepository();
|
||||||
|
$this->userRepository = new UserRepository();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getToken(): ?IContent
|
||||||
|
{
|
||||||
|
$token = $this->oAuthTokenRepository->getByCode($this->request->post('code'));
|
||||||
|
|
||||||
|
if ($token === null || $token->getExpiresDate() < new DateTime()) {
|
||||||
|
return new JsonContent([
|
||||||
|
'error' => 'The provided code is invalid.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = array_merge([
|
||||||
|
'iss' => $_ENV['APP_URL'],
|
||||||
|
'iat' => (int)$token->getCreatedDate()->getTimestamp(),
|
||||||
|
'nbf' => (int)$token->getCreatedDate()->getTimestamp(),
|
||||||
|
'exp' => (int)$token->getExpiresDate()->getTimestamp(),
|
||||||
|
'nonce' => $token->getNonce()
|
||||||
|
], $this->getUserInfoInternal(
|
||||||
|
$this->userRepository->getById($token->getUserId()),
|
||||||
|
$token->getScopeArray())
|
||||||
|
);
|
||||||
|
|
||||||
|
$privateKey = file_get_contents(ROOT . '/' . $_ENV['JWT_RSA_PRIVATE_KEY']);
|
||||||
|
$jwt = JWT::encode($payload, $privateKey, 'RS256');
|
||||||
|
|
||||||
|
return new JsonContent([
|
||||||
|
'access_token' => $token->getAccessToken(),
|
||||||
|
'expires_in' => $token->getExpiresDate()->getTimestamp() - (new DateTime())->getTimestamp(),
|
||||||
|
'scope' => $token->getScope(),
|
||||||
|
'id_token' => $jwt,
|
||||||
|
'token_type' => 'Bearer'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserInfo() : IContent
|
||||||
|
{
|
||||||
|
//TODO: headers should be set by soko-web
|
||||||
|
$headers = getallheaders();
|
||||||
|
|
||||||
|
if (!isset($headers['Authorization'])) {
|
||||||
|
return new JsonContent([
|
||||||
|
'error' => 'No Authorization header was sent.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$accessToken = substr($headers['Authorization'], strlen('Bearer '));
|
||||||
|
$token = $this->oAuthTokenRepository->getByAccessToken($accessToken);
|
||||||
|
|
||||||
|
if ($token === null || $token->getExpiresDate() < new DateTime()) {
|
||||||
|
return new JsonContent([
|
||||||
|
'error' => 'The provided access token is invalid.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JsonContent(
|
||||||
|
$this->getUserInfoInternal(
|
||||||
|
$this->userRepository->getById($token->getUserId()),
|
||||||
|
$token->getScopeArray()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConfig(): IContent
|
||||||
|
{
|
||||||
|
return new JsonContent([
|
||||||
|
'issuer' => $_ENV['APP_URL'],
|
||||||
|
'authorization_endpoint' => $this->request->getBase() . '/oauth/auth',
|
||||||
|
'token_endpoint' => $this->request->getBase() . '/oauth/token',
|
||||||
|
'userinfo_endpoint' => $this->request->getBase() . '/oauth/userinfo',
|
||||||
|
'jwks_uri' => $this->request->getBase() . '/oauth/certs',
|
||||||
|
'response_types_supported' =>
|
||||||
|
[
|
||||||
|
'code',
|
||||||
|
],
|
||||||
|
'subject_types_supported' =>
|
||||||
|
[
|
||||||
|
'public',
|
||||||
|
],
|
||||||
|
'id_token_signing_alg_values_supported' =>
|
||||||
|
[
|
||||||
|
'RS256',
|
||||||
|
],
|
||||||
|
'scopes_supported' =>
|
||||||
|
[
|
||||||
|
'openid',
|
||||||
|
'email',
|
||||||
|
'profile',
|
||||||
|
],
|
||||||
|
'token_endpoint_auth_methods_supported' =>
|
||||||
|
[
|
||||||
|
'client_secret_post',
|
||||||
|
],
|
||||||
|
'claims_supported' =>
|
||||||
|
[
|
||||||
|
'aud',
|
||||||
|
'email',
|
||||||
|
'exp',
|
||||||
|
'full_name',
|
||||||
|
'iat',
|
||||||
|
'id_number',
|
||||||
|
'iss',
|
||||||
|
'nickname',
|
||||||
|
'phone',
|
||||||
|
'picture',
|
||||||
|
'sub',
|
||||||
|
'username',
|
||||||
|
],
|
||||||
|
'code_challenge_methods_supported' =>
|
||||||
|
[
|
||||||
|
'plain',
|
||||||
|
'S256',
|
||||||
|
],
|
||||||
|
'grant_types_supported' =>
|
||||||
|
[
|
||||||
|
'authorization_code',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCerts(): IContent
|
||||||
|
{
|
||||||
|
$publicKey = file_get_contents(ROOT . '/' . $_ENV['JWT_RSA_PUBLIC_KEY']);
|
||||||
|
$keyInfo = openssl_pkey_get_details(openssl_pkey_get_public($publicKey));
|
||||||
|
|
||||||
|
return new JsonContent(['keys' => [
|
||||||
|
[
|
||||||
|
'kty' => 'RSA',
|
||||||
|
'alg' => 'RS256',
|
||||||
|
'use' => 'sig',
|
||||||
|
'kid' => '1',
|
||||||
|
'n' => str_replace(['+', '/'], ['-', '_'], base64_encode($keyInfo['rsa']['n'])),
|
||||||
|
'e' => str_replace(['+', '/'], ['-', '_'], base64_encode($keyInfo['rsa']['e'])),
|
||||||
|
]
|
||||||
|
]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getUserInfoInternal(User $user, array $scope): array
|
||||||
|
{
|
||||||
|
$userInfo = [];
|
||||||
|
if (in_array('openid', $scope)) {
|
||||||
|
$userInfo['sub'] = (string)$user->getId();
|
||||||
|
}
|
||||||
|
if (in_array('email', $scope)) {
|
||||||
|
$userInfo['email'] = $user->getEmail();
|
||||||
|
}
|
||||||
|
if (in_array('profile', $scope)) {
|
||||||
|
if ($user->getUsername() !== null) {
|
||||||
|
$userInfo['preferred_username'] = $user->getUsername();
|
||||||
|
}
|
||||||
|
$userInfo['name'] = $user->getFullName();
|
||||||
|
$userInfo['nickname'] = $user->getNickname();
|
||||||
|
$userInfo['phone_number'] = $user->getPhone();
|
||||||
|
$userInfo['id_number'] = $user->getIdNumber();
|
||||||
|
}
|
||||||
|
return $userInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ use SokoWeb\Response\HtmlContent;
|
|||||||
use SokoWeb\Response\JsonContent;
|
use SokoWeb\Response\JsonContent;
|
||||||
use SokoWeb\Response\Redirect;
|
use SokoWeb\Response\Redirect;
|
||||||
use SokoWeb\Util\JwtParser;
|
use SokoWeb\Util\JwtParser;
|
||||||
|
use RVR\Repository\UserRepository;
|
||||||
|
|
||||||
class UserController implements ISecured
|
class UserController implements ISecured
|
||||||
{
|
{
|
||||||
@@ -20,10 +21,13 @@ class UserController implements ISecured
|
|||||||
|
|
||||||
private PersistentDataManager $pdm;
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
|
private UserRepository $userRepository;
|
||||||
|
|
||||||
public function __construct(IRequest $request)
|
public function __construct(IRequest $request)
|
||||||
{
|
{
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
$this->pdm = new PersistentDataManager();
|
$this->pdm = new PersistentDataManager();
|
||||||
|
$this->userRepository = new UserRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
@@ -126,8 +130,39 @@ class UserController implements ISecured
|
|||||||
return new JsonContent(['error' => ['errorText' => $error]]);
|
return new JsonContent(['error' => ['errorText' => $error]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strlen($this->request->post('password_new')) > 0) {
|
$newEmail = $this->request->post('email');
|
||||||
if (strlen($this->request->post('password_new')) < 6) {
|
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([
|
return new JsonContent([
|
||||||
'error' => [
|
'error' => [
|
||||||
'errorText' => 'The given new password is too short. Please choose a password that is at least 6 characters long!'
|
'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([
|
return new JsonContent([
|
||||||
'error' => [
|
'error' => [
|
||||||
'errorText' => 'The given new passwords do not match.'
|
'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->pdm->saveToDb($user);
|
||||||
|
|
||||||
$this->request->session()->delete('authenticated_with_google_until');
|
$this->request->session()->delete('authenticated_with_google_until');
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
<?php namespace RVR\PersistentData\Model;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use SokoWeb\PersistentData\Model\Model;
|
||||||
|
|
||||||
|
class OAuthToken extends Model
|
||||||
|
{
|
||||||
|
protected static string $table = 'oauth_tokens';
|
||||||
|
|
||||||
|
protected static array $fields = ['scope', 'nonce', 'user_id', 'code', 'access_token', 'created', 'expires'];
|
||||||
|
|
||||||
|
protected static array $relations = ['user' => User::class];
|
||||||
|
|
||||||
|
private static array $possibleScopeValues = ['openid', 'email', 'profile'];
|
||||||
|
|
||||||
|
private array $scope = [];
|
||||||
|
|
||||||
|
private string $nonce = '';
|
||||||
|
|
||||||
|
private ?User $user = null;
|
||||||
|
|
||||||
|
private ?int $userId = null;
|
||||||
|
|
||||||
|
private string $code = '';
|
||||||
|
|
||||||
|
private string $accessToken = '';
|
||||||
|
|
||||||
|
private DateTime $created;
|
||||||
|
|
||||||
|
private DateTime $expires;
|
||||||
|
|
||||||
|
public function setScopeArray(array $scope): void
|
||||||
|
{
|
||||||
|
$this->scope = array_intersect($scope, self::$possibleScopeValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setScope(string $scope): void
|
||||||
|
{
|
||||||
|
$this->setScopeArray(explode(' ', $scope));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setNonce(string $nonce): void
|
||||||
|
{
|
||||||
|
$this->nonce = $nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUser(User $user): void
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUserId(int $userId): void
|
||||||
|
{
|
||||||
|
$this->userId = $userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCode(string $code): void
|
||||||
|
{
|
||||||
|
$this->code = $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAccessToken(string $accessToken): void
|
||||||
|
{
|
||||||
|
$this->accessToken = $accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCreatedDate(DateTime $created): void
|
||||||
|
{
|
||||||
|
$this->created = $created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setExpiresDate(DateTime $expires): void
|
||||||
|
{
|
||||||
|
$this->expires = $expires;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCreated(string $created): void
|
||||||
|
{
|
||||||
|
$this->created = new DateTime($created);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setExpires(string $expires): void
|
||||||
|
{
|
||||||
|
$this->expires = new DateTime($expires);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getScope(): string
|
||||||
|
{
|
||||||
|
return implode(' ', $this->scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getScopeArray(): array
|
||||||
|
{
|
||||||
|
return $this->scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNonce(): string
|
||||||
|
{
|
||||||
|
return $this->nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser(): ?User
|
||||||
|
{
|
||||||
|
return $this->user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserId(): ?int
|
||||||
|
{
|
||||||
|
return $this->userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCode(): string
|
||||||
|
{
|
||||||
|
return $this->code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAccessToken(): string
|
||||||
|
{
|
||||||
|
return $this->accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedDate(): DateTime
|
||||||
|
{
|
||||||
|
return $this->created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreated(): string
|
||||||
|
{
|
||||||
|
return $this->created->format('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExpiresDate(): DateTime
|
||||||
|
{
|
||||||
|
return $this->expires;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExpires(): string
|
||||||
|
{
|
||||||
|
return $this->expires->format('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,12 +8,14 @@ class User extends Model implements IUser
|
|||||||
{
|
{
|
||||||
protected static string $table = 'users';
|
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 static array $types = ['user', 'admin'];
|
||||||
|
|
||||||
private string $email = '';
|
private string $email = '';
|
||||||
|
|
||||||
|
private ?string $username = null;
|
||||||
|
|
||||||
private ?string $password = null;
|
private ?string $password = null;
|
||||||
|
|
||||||
private string $type = 'user';
|
private string $type = 'user';
|
||||||
@@ -22,11 +24,24 @@ class User extends Model implements IUser
|
|||||||
|
|
||||||
private DateTime $created;
|
private DateTime $created;
|
||||||
|
|
||||||
|
private string $fullName = '';
|
||||||
|
|
||||||
|
private string $nickname = '';
|
||||||
|
|
||||||
|
private string $phone = '';
|
||||||
|
|
||||||
|
private string $idNumber = '';
|
||||||
|
|
||||||
public function setEmail(string $email): void
|
public function setEmail(string $email): void
|
||||||
{
|
{
|
||||||
$this->email = $email;
|
$this->email = $email;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setUsername(?string $username): void
|
||||||
|
{
|
||||||
|
$this->username = $username;
|
||||||
|
}
|
||||||
|
|
||||||
public function setPassword(?string $hashedPassword): void
|
public function setPassword(?string $hashedPassword): void
|
||||||
{
|
{
|
||||||
$this->password = $hashedPassword;
|
$this->password = $hashedPassword;
|
||||||
@@ -59,11 +74,36 @@ class User extends Model implements IUser
|
|||||||
$this->created = new DateTime($created);
|
$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
|
public function getEmail(): string
|
||||||
{
|
{
|
||||||
return $this->email;
|
return $this->email;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getUsername(): ?string
|
||||||
|
{
|
||||||
|
return $this->username;
|
||||||
|
}
|
||||||
|
|
||||||
public function getPassword(): ?string
|
public function getPassword(): ?string
|
||||||
{
|
{
|
||||||
return $this->password;
|
return $this->password;
|
||||||
@@ -89,6 +129,26 @@ class User extends Model implements IUser
|
|||||||
return $this->created->format('Y-m-d H:i:s');
|
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
|
public function hasPermission(int $permission): bool
|
||||||
{
|
{
|
||||||
switch ($permission) {
|
switch ($permission) {
|
||||||
@@ -108,7 +168,7 @@ class User extends Model implements IUser
|
|||||||
|
|
||||||
public function getDisplayName(): string
|
public function getDisplayName(): string
|
||||||
{
|
{
|
||||||
return $this->email;
|
return $this->nickname ?: $this->fullName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function checkPassword(string $password): bool
|
public function checkPassword(string $password): bool
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?php namespace RVR\Repository;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use Generator;
|
||||||
|
use SokoWeb\Database\Query\Select;
|
||||||
|
use RVR\PersistentData\Model\OAuthToken;
|
||||||
|
use SokoWeb\PersistentData\PersistentDataManager;
|
||||||
|
|
||||||
|
class OAuthTokenRepository
|
||||||
|
{
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getById(int $id): ?OAuthToken
|
||||||
|
{
|
||||||
|
return $this->pdm->selectFromDbById($id, OAuthToken::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getByCode(string $code): ?OAuthToken
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('code', '=', $code);
|
||||||
|
|
||||||
|
return $this->pdm->selectFromDb($select, OAuthToken::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getByAccessToken(string $accessToken): ?OAuthToken
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('access_token', '=', $accessToken);
|
||||||
|
|
||||||
|
return $this->pdm->selectFromDb($select, OAuthToken::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllExpired(): Generator
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('expires', '<', (new DateTime())->format('Y-m-d H:i:s'));
|
||||||
|
|
||||||
|
yield from $this->pdm->selectMultipleFromDb($select, OAuthToken::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,23 @@ class UserRepository implements IUserRepository
|
|||||||
return $this->pdm->selectFromDb($select, User::class);
|
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
|
public function getByGoogleSub(string $sub): ?User
|
||||||
{
|
{
|
||||||
$select = new Select(\Container::$dbConnection);
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
@section(main)
|
@section(main)
|
||||||
<h2>Account</h2>
|
<h2>Account</h2>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<form id="accountForm" action="/account" method="post" data-observe-inputs="password_new,password_new_confirm">
|
<form id="accountForm" action="/account" method="post" data-reload-on-success="true" data-observe-inputs="email,username,password_new,password_new_confirm,nickname,phone,id_number">
|
||||||
<?php if ($user['password'] !== null && $user['google_sub'] !== null): ?>
|
<?php if ($user['password'] !== null && $user['google_sub'] !== null): ?>
|
||||||
<p class="justify small">Please confirm your identity with your password or with Google to modify your account.</p>
|
<p class="justify small">Please confirm your identity with your password or with Google to modify your account.</p>
|
||||||
<div class="inputWithButton">
|
<div class="inputWithButton">
|
||||||
<input type="password" class="text name="password" placeholder="Current password" required minlength="6" autofocus><!--
|
<input type="password" class="text" name="password" placeholder="Current password" required minlength="6" autofocus><!--
|
||||||
--><button id="authenticateWithGoogleButton" class="yellow" type="button">Google</button>
|
--><button id="authenticateWithGoogleButton" class="yellow" type="button">Google</button>
|
||||||
</div>
|
</div>
|
||||||
<?php elseif ($user['password'] !== null): ?>
|
<?php elseif ($user['password'] !== null): ?>
|
||||||
@@ -23,10 +23,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<hr>
|
<hr>
|
||||||
<?php /* TODO: disabled for the time being, email modification should be implemented */ ?>
|
<input type="email" class="text big fullWidth" name="email" placeholder="Email address" value="<?= $user['email'] ?>">
|
||||||
<input type="email" class="text big fullWidth" name="email" placeholder="Email address" value="<?= $user['email'] ?>" disabled>
|
<input type="text" class="text big fullWidth marginTop" name="username" placeholder="Username" value="<?= $user['username'] ?>">
|
||||||
<input type="password" class="text big fullWidth marginTop" name="password_new" placeholder="New password" minlength="6">
|
<input type="password" class="text big fullWidth marginTop" name="password_new" placeholder="New password" minlength="6">
|
||||||
<input type="password" class="text big fullWidth marginTop" name="password_new_confirm" placeholder="New password confirmation" minlength="6">
|
<input type="password" class="text big fullWidth marginTop" name="password_new_confirm" placeholder="New password confirmation" minlength="6">
|
||||||
|
<hr>
|
||||||
|
<input type="text" class="text big fullWidth marginTop" name="full_name" placeholder="Full name" value="<?= $user['full_name'] ?>" disabled>
|
||||||
|
<input type="text" class="text big fullWidth marginTop" name="nickname" placeholder="Nickname" value="<?= $user['nickname'] ?>">
|
||||||
|
<input type="text" class="text big fullWidth marginTop" name="phone" placeholder="Phone" value="<?= $user['phone'] ?>">
|
||||||
|
<input type="text" class="text big fullWidth marginTop" name="id_number" placeholder="ID number" value="<?= $user['id_number'] ?>">
|
||||||
<p id="accountFormError" class="formError justify marginTop"></p>
|
<p id="accountFormError" class="formError justify marginTop"></p>
|
||||||
<div class="right marginTop">
|
<div class="right marginTop">
|
||||||
<button type="submit" name="submit" disabled>Save</button>
|
<button type="submit" name="submit" disabled>Save</button>
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
@extends(templates/layout_normal)
|
|
||||||
|
|
||||||
@section(main)
|
|
||||||
<h2>Login up with Google</h2>
|
|
||||||
<div class="box">
|
|
||||||
<p class="error justify">Authentication with Google failed. Please <a href="/login/google" title="Login with Google">try again</a>!</p>
|
|
||||||
</div>
|
|
||||||
@endsection
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
@extends(templates/layout_normal)
|
||||||
|
|
||||||
|
@section(main)
|
||||||
|
<h2>Login up with Google</h2>
|
||||||
|
<div class="box">
|
||||||
|
<p class="error justify"><?= $error ?></p>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
@section(main)
|
@section(main)
|
||||||
<h2>Login</h2>
|
<h2>Login</h2>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<form id="loginForm" action="/login" method="post" data-redirect-on-success="/">
|
<form id="loginForm" action="/login" method="post" data-redirect-on-success="<?= $redirectUrl ?>">
|
||||||
<input type="email" class="text big fullWidth" name="email" placeholder="Email address" required autofocus>
|
<input type="text" class="text big fullWidth" name="email" placeholder="Email address / Username" required autofocus>
|
||||||
<input type="password" class="text big fullWidth marginTop" name="password" placeholder="Password" required minlength="6">
|
<input type="password" class="text big fullWidth marginTop" name="password" placeholder="Password" required minlength="6">
|
||||||
<p id="loginFormError" class="formError justify marginTop"></p>
|
<p id="loginFormError" class="formError justify marginTop"></p>
|
||||||
<div class="right marginTop">
|
<div class="right marginTop">
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<h2>Reset password</h2>
|
<h2>Reset password</h2>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<?php if ($success) : ?>
|
<?php if ($success) : ?>
|
||||||
<form id="resetPasswordForm" action="/password/reset/<?= $token ?>" method="post" data-redirect-on-success="/">
|
<form id="resetPasswordForm" action="/password/reset/<?= $token ?>" method="post" data-redirect-on-success="<?= $redirectUrl ?>">
|
||||||
<input type="email" class="text big fullWidth" name="email" placeholder="Email address" value="<?= $email ?>" disabled>
|
<input type="email" class="text big fullWidth" name="email" placeholder="Email address" value="<?= $email ?>" disabled>
|
||||||
<input type="password" class="text big fullWidth marginTop" name="password" placeholder="Password" required minlength="6" autofocus>
|
<input type="password" class="text big fullWidth marginTop" name="password" placeholder="Password" required minlength="6" autofocus>
|
||||||
<input type="password" class="text big fullWidth marginTop" name="password_confirm" placeholder="Password confirmation" required minlength="6">
|
<input type="password" class="text big fullWidth marginTop" name="password_confirm" placeholder="Password confirmation" required minlength="6">
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
@extends(templates/layout_normal)
|
||||||
|
|
||||||
|
@section(main)
|
||||||
|
<h2>OAuth error</h2>
|
||||||
|
<div class="box">
|
||||||
|
<p class="error justify"><?= $error ?></p>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
@@ -20,6 +20,13 @@ Container::$routeCollection->group('login', function (SokoWeb\Routing\RouteColle
|
|||||||
$routeCollection->get('login-google', 'google', [RVR\Controller\LoginController::class, 'getGoogleLoginRedirect']);
|
$routeCollection->get('login-google', 'google', [RVR\Controller\LoginController::class, 'getGoogleLoginRedirect']);
|
||||||
$routeCollection->get('login-google-action', 'google/code', [RVR\Controller\LoginController::class, 'loginWithGoogle']);
|
$routeCollection->get('login-google-action', 'google/code', [RVR\Controller\LoginController::class, 'loginWithGoogle']);
|
||||||
});
|
});
|
||||||
|
Container::$routeCollection->group('oauth', function (SokoWeb\Routing\RouteCollection $routeCollection) {
|
||||||
|
$routeCollection->get('oauth-auth', 'auth', [RVR\Controller\OAuthAuthController::class, 'auth']);
|
||||||
|
$routeCollection->post('oauth-token', 'token', [RVR\Controller\OAuthController::class, 'getToken']);
|
||||||
|
$routeCollection->get('oauth-userinfo', 'userinfo', [RVR\Controller\OAuthController::class, 'getUserInfo']);
|
||||||
|
$routeCollection->get('oauth-config', '.well-known/openid-configuration', [RVR\Controller\OAuthController::class, 'getConfig']);
|
||||||
|
$routeCollection->get('oauth-certs', 'certs', [RVR\Controller\OAuthController::class, 'getCerts']);
|
||||||
|
});
|
||||||
Container::$routeCollection->group('password', function (SokoWeb\Routing\RouteCollection $routeCollection) {
|
Container::$routeCollection->group('password', function (SokoWeb\Routing\RouteCollection $routeCollection) {
|
||||||
$routeCollection->get('password-requestReset', 'requestReset', [RVR\Controller\LoginController::class, 'getRequestPasswordResetForm']);
|
$routeCollection->get('password-requestReset', 'requestReset', [RVR\Controller\LoginController::class, 'getRequestPasswordResetForm']);
|
||||||
$routeCollection->post('password-requestReset-action', 'requestReset', [RVR\Controller\LoginController::class, 'requestPasswordReset']);
|
$routeCollection->post('password-requestReset-action', 'requestReset', [RVR\Controller\LoginController::class, 'requestPasswordReset']);
|
||||||
@@ -72,3 +79,6 @@ Container::$request = new SokoWeb\Request\Request(
|
|||||||
if (!Container::$request->session()->has('anti_csrf_token')) {
|
if (!Container::$request->session()->has('anti_csrf_token')) {
|
||||||
Container::$request->session()->set('anti_csrf_token', bin2hex(random_bytes(16)));
|
Container::$request->session()->set('anti_csrf_token', bin2hex(random_bytes(16)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: make a nicer logic
|
||||||
|
$antiCsrfTokenExceptions = ['oauth/token'];
|
||||||
Reference in new issue
Block a user