Compare commits

..
Author SHA1 Message Date
bence 882b326aca Merge pull request 'merge new and edit methods in CommunityController' (!33) from feature/unify-saves-in-communitycontroller into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #33
2023-04-26 09:46:09 +02:00
bence 49fe85dcde merge new and edit methods in CommunityController
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-26 09:43:57 +02:00
bence a021aff92c Merge pull request 'feature/RVRNEXT-5-handling-currency-exchange-rates' (!32) from feature/RVRNEXT-5-handling-currency-exchange-rates into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #32
2023-04-25 19:32:11 +02:00
bence 6abe93e3b5 RVRNEXT-5 fine-tune styles
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-25 19:28:42 +02:00
bence 7e84e6b4ba RVRNEXT-5 add/update layout for currency exchange rates 2023-04-25 19:28:42 +02:00
bence 76ed977375 RVRNEXT-5 add logic for handling currency exchange rates 2023-04-25 19:28:42 +02:00
bence fea403fe5a RVRNEXT-5 add new routes for currency exchange rates 2023-04-25 19:28:42 +02:00
bence 1d96fd0cb9 RVRNEXT-5 add model and repository for currency exchange rate 2023-04-25 19:28:42 +02:00
bence 7437e19de5 RVRNEXT-5 add migration for currency exchange rates 2023-04-23 20:50:45 +02:00
bence 47567c9b75 RVRNEXT-5 add links to community main page 2023-04-23 20:46:11 +02:00
bence ef16e70235 Merge pull request 'feature/RVRNEXT-5-handling-currencies' (!31) from feature/RVRNEXT-5-handling-currencies into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #31
2023-04-23 20:21:31 +02:00
bence 91d064947e RVRNEXT-5 formError should be searched in the whole document
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-23 20:20:10 +02:00
bence 9a6e95673e RVRNEXT-5 add formError for multi for layouts 2023-04-23 20:20:10 +02:00
bence d24c3bb1cb RVRNEXT-5 move private methods to the end of CommunityController 2023-04-23 20:20:10 +02:00
bence 2ad637e55f RVRNEXT-5 make prepare-commit-msg accept fixup commits 2023-04-23 20:20:10 +02:00
bence ac09342c9d RVRNEXT-5 fix layouts for communities 2023-04-23 20:20:10 +02:00
bence 3a1054e1df RVRNEXT-5 adjust table style 2023-04-23 20:20:10 +02:00
bence 0f02c4b5be RVRNEXT-5 update CommunityController with currency operations 2023-04-23 20:20:10 +02:00
bence be0a15d02d RVRNEXT-5 extend views with currency edit 2023-04-23 20:09:49 +02:00
bence 12065b0525 RVRNEXT-5 add model and repository for currencies 2023-04-23 20:09:44 +02:00
bence 0fc21cc461 RVRNEXT-5 add migration for currencies 2023-04-23 20:09:44 +02:00
bence 7d63de4403 RVRNEXT-5 add new endpoints for currency handling 2023-04-23 17:36:03 +02:00
bence c520c256ee Merge pull request 'solve community member edit with forms' (!30) from feature/solve-community-member-edit-with-forms into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #30
2023-04-23 15:59:32 +02:00
bence 2f9b97d1a4 solve community member edit with forms
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-23 15:55:31 +02:00
bence ad18c7cb25 Merge pull request 'use IRouteCollection in app container' (!29) from bugfix/fix-app-container into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #29
2023-04-20 00:34:26 +02:00
bence a2fb166b55 use IRouteCollection in app container
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-20 00:33:06 +02:00
bence f0a7b34212 Merge pull request 'feature/update-soko-web' (!28) from feature/update-soko-web into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #28
2023-04-19 23:43:27 +02:00
bence dccb34971b adapt Container usage to new soko-web
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-19 23:38:43 +02:00
bence 36f767ea9b update soko-web version 2023-04-19 23:38:43 +02:00
bence c0739eeddf Merge pull request 'feature/activate-audit-logging' (!27) from feature/activate-audit-logging into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #27
2023-04-19 00:58:44 +02:00
bence cac30c9203 add auditLogger instance to app container
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-18 23:37:55 +02:00
bence 89b0ce768b add db table for audit log 2023-04-18 23:37:55 +02:00
bence 656ce219ef add class AuditLogger 2023-04-18 23:37:55 +02:00
bence fd30911dd3 Merge pull request 'feature/update-to-soko-web-0.5' (!26) from feature/update-to-soko-web-0.5 into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #26
2023-04-18 23:30:33 +02:00
bence b4cfa7aab2 remove unnecessary startTransaction and commit calls
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-18 23:29:04 +02:00
bence 617ae903ae add error view 500 to app config 2023-04-18 23:29:04 +02:00
bence a7997429b7 add view for error 500 2023-04-18 23:26:42 +02:00
bence 4d5e982370 pass dbConnection to HttpResponse 2023-04-18 23:26:42 +02:00
bence 46ea01efe1 update soko-web to 0.5 2023-04-18 23:20:29 +02:00
bence 4161abe098 Merge pull request 'fix path for antiCsrfTokenExceptions' (!25) from bugfix/fix-path-for-csrf-exceptions into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #25
2023-04-16 22:09:15 +02:00
bence 8777d931a4 fix path for antiCsrfTokenExceptions
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-16 22:07:57 +02:00
bence d2ef9bb18a Merge pull request 'remove unnecessary use in web.php' (!24) from bugfix/remove-unnecessary-use into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #24
2023-04-16 22:07:30 +02:00
bence 3ec7fc11fa remove unnecessary use in web.php
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-16 22:01:48 +02:00
bence 9fd8453f63 Merge pull request 'feature/adapt-to-new-soko-web' (!23) from feature/adapt-to-new-soko-web into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #23
2023-04-16 21:16:58 +02:00
bence a7d3942d1f use classes at beginning of web.php
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-16 21:14:56 +02:00
bence 41933ec510 update soko-web to 0.4
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-16 20:54:49 +02:00
bence e98bb28faf adapt to soko-web 0.4 2023-04-16 20:54:29 +02:00
bence 34eeb10c27 Merge pull request 'feature/start-using-authentication-required-interface' (!22) from feature/start-using-authentication-required-interface into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #22
2023-04-16 17:53:16 +02:00
bence 70e13f70c0 update soko-web to 0.3
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-16 17:51:29 +02:00
bence a89182b64f split authentication required and secured controllers 2023-04-16 17:51:29 +02:00
bence 45a22c2dd4 Merge pull request 'feature/upgrade-soko-web' (!21) from feature/upgrade-soko-web into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #21
2023-04-16 16:05:51 +02:00
bence c6d33753c8 adapt to new soko-web
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-16 16:03:38 +02:00
bence b32092f1cd upgrade soko-web to 0.2.1 2023-04-16 16:03:11 +02:00
bence b5cb59d3ee Merge pull request 'replace hardcoded links to generated ones' (!20) from feature/replace-hardcoded-links into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #20
2023-04-16 14:44:00 +02:00
bence 0b8d7b9cda replace hardcoded links to generated ones
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-16 14:31:10 +02:00
bence 4f4cc43e90 fix home links in LoginController 2023-04-16 14:30:49 +02:00
bence b066ef22b4 Merge pull request 'add adminer to docker compose stack' (!19) from feature/add-adminer into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #19
2023-04-16 14:24:02 +02:00
bence 08767d1d59 add adminer to docker compose stack
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-16 14:21:19 +02:00
bence 46404a9d7b Merge pull request 'fix syntax error in migration' (!18) from hotfix/fix-migration into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #18
2023-04-16 14:07:00 +02:00
bence 8a4c5403cb fix syntax error in migration
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-16 14:05:14 +02:00
bence 15a58e8ace Merge pull request 'implement community basics' (!17) from feature/community-basics into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #17
2023-04-16 14:00:25 +02:00
bence d763c4344c implement community basics
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-16 13:57:54 +02:00
bence 4dc08dffc9 Merge pull request 'feature/fix-oauth-audience' (!16) from feature/fix-oauth-audience into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #16
2023-04-12 02:11:53 +02:00
bence 87b811f716 client id can be anything
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-12 02:10:33 +02:00
bence eb4ebb9582 send clientId as aud 2023-04-12 02:05:44 +02:00
bence 6af7813e3d drop audience from oauth_tokens 2023-04-12 02:04:56 +02:00
bence 31ff9b287d Revert "add aud to jwt"
This reverts commit 97780eb079.
2023-04-12 02:03:15 +02:00
bence c9a06e5ada Merge pull request 'add aud to jwt' (!15) from feature/add-oauth-audience into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #15
2023-04-12 00:44:52 +02:00
bence 4f007765f4 Merge pull request 'omit whitespace in main layout' (!14) from bugfix/layout-fix into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #14
2023-04-12 00:42:40 +02:00
bence 97780eb079 add aud to jwt
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-12 00:41:20 +02:00
bence e487a59816 Merge pull request 'restrict oauth access' (!13) from feature/oauth-restrictions into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #13
2023-04-12 00:15:08 +02:00
bence a7790319eb restrict oauth access
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-12 00:10:14 +02:00
bence e143d05801 Merge pull request 'make oauth endpoints openid compliant' (!12) from oauth-improvements into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #12
2023-04-11 19:47:54 +02:00
bence 7c0ebe6668 fix url passed to 'redirect_after_login'
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-11 19:06:22 +02:00
bence 6eb54a6f11 make oauth endpoints openid compliant 2023-04-11 19:06:22 +02:00
bence ed137b38de omit whitespace in main layout
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-09 03:16:45 +02:00
66 changed files with 2120 additions and 411 deletions

No files matched your search

+5 -1
View File
@@ -15,9 +15,13 @@ $dotenv->load();
class Container
{
static SokoWeb\Interfaces\Database\IConnection $dbConnection;
static SokoWeb\Routing\RouteCollection $routeCollection;
static SokoWeb\Interfaces\Database\IAuditLogger $auditLogger;
static SokoWeb\Interfaces\PersistentData\IPersistentDataManager $persistentDataManager;
static SokoWeb\Interfaces\Routing\IRouteCollection $routeCollection;
static SokoWeb\Interfaces\Session\ISessionHandler $sessionHandler;
static SokoWeb\Interfaces\Request\IRequest $request;
}
Container::$dbConnection = new SokoWeb\Database\Mysql\Connection($_ENV['DB_HOST'], $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], $_ENV['DB_NAME']);
Container::$auditLogger = new RVR\Database\AuditLogger(Container::$dbConnection, 'audit_log');
Container::$persistentDataManager = new SokoWeb\PersistentData\PersistentDataManager(Container::$dbConnection, Container::$auditLogger);
+1 -1
View File
@@ -10,7 +10,7 @@
}
],
"require": {
"esoko/soko-web": "0.1",
"esoko/soko-web": "0.6",
"firebase/php-jwt": "^6.4"
},
"require-dev": {
Generated
+4 -4
View File
@@ -4,15 +4,15 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "f2dcf297a4a619bc5edfe7b4fac0836e",
"content-hash": "7b9e3854a5b7ef69d360c1dfbac75c17",
"packages": [
{
"name": "esoko/soko-web",
"version": "v0.1",
"version": "0.6",
"source": {
"type": "git",
"url": "https://git.esoko.eu/esoko/soko-web.git",
"reference": "a17a88e9d44e42cef3d996fc66b4dd1134b85def"
"reference": "5e0579463cf0f4203c46e0d4f9c09cd283dbf0b8"
},
"require": {
"phpmailer/phpmailer": "^6.8",
@@ -33,7 +33,7 @@
"GNU GPL 3.0"
],
"description": "Lightweight web framework",
"time": "2023-04-07T17:32:15+00:00"
"time": "2023-04-19T21:35:03+00:00"
},
{
"name": "firebase/php-jwt",
@@ -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`);
@@ -0,0 +1,10 @@
CREATE TABLE `oauth_clients` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`client_id` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
`client_secret` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
`redirect_uris` text NOT NULL,
`preapproved` tinyint(1) NOT NULL DEFAULT 0,
`created` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `client_id` (`client_id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
@@ -0,0 +1,2 @@
ALTER TABLE `oauth_tokens`
ADD `audience` varchar(255) NOT NULL DEFAULT '';
@@ -0,0 +1,2 @@
ALTER TABLE `oauth_tokens`
DROP `audience`;
@@ -0,0 +1,2 @@
ALTER TABLE `oauth_clients`
MODIFY `client_id` varchar(255) CHARACTER SET ascii COLLATE ascii_bin NOT NULL;
@@ -0,0 +1,20 @@
CREATE TABLE `communities` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`currency` varchar(3) NOT NULL,
`created` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
CREATE TABLE `community_members` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`community_id` int(10) unsigned NOT NULL,
`user_id` int(10) unsigned DEFAULT NULL,
`owner` tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_user_for_community` (`community_id`, `user_id`),
KEY `community_id` (`community_id`),
KEY `user_id` (`user_id`),
CONSTRAINT `community_members_community_id` FOREIGN KEY (`community_id`) REFERENCES `communities` (`id`),
CONSTRAINT `community_members_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
@@ -0,0 +1,12 @@
CREATE TABLE `audit_log` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`local_table` varchar(255) NOT NULL,
`local_id` int(10) unsigned NOT NULL,
`type` enum('insert','update','delete') NOT NULL,
`date` timestamp NOT NULL DEFAULT current_timestamp(),
`modifier_id` int(10) unsigned NULL,
`column` varchar(255) NULL,
`old` text NULL,
`new` text NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
@@ -0,0 +1,10 @@
CREATE TABLE `currencies` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`community_id` int(10) unsigned NOT NULL,
`code` varchar(3) NOT NULL,
`round_digits` tinyint(1) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_currency_for_community` (`community_id`, `code`),
KEY `community_id` (`community_id`),
CONSTRAINT `currencies_community_id` FOREIGN KEY (`community_id`) REFERENCES `communities` (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
@@ -0,0 +1,10 @@
CREATE TABLE `currency_exchange_rates` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`currency_id` int(10) unsigned NOT NULL,
`exchange_rate` decimal(19,9) unsigned NOT NULL,
`valid_from` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `currency_id` (`currency_id`),
INDEX `valid_from` (`valid_from`),
CONSTRAINT `currency_exchange_rates_currency_id` FOREIGN KEY (`currency_id`) REFERENCES `currencies` (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
+6
View File
@@ -19,6 +19,12 @@ services:
MYSQL_DATABASE: 'rvr'
MYSQL_USER: 'rvr'
MYSQL_PASSWORD: 'rvr'
adminer:
image: adminer:4.8.1-standalone
ports:
- 9090:8080
environment:
- ADMINER_DEFAULT_SERVER=mariadb
mail:
image: marcopas/docker-mailslurper:latest
ports:
-55
View File
@@ -1,58 +1,3 @@
<?php
use SokoWeb\Interfaces\Response\IRedirect;
use SokoWeb\Response\Redirect;
require '../web.php';
$method = strtolower($_SERVER['REQUEST_METHOD']);
$url = substr($_SERVER['REQUEST_URI'], strlen('/'));
if (($pos = strpos($url, '?')) !== false) {
$url = substr($url, 0, $pos);
}
$url = rawurldecode($url);
$match = Container::$routeCollection->match($method, $url == '' ? [] : explode('/', $url));
if ($match !== null) {
list($route, $params) = $match;
Container::$request->setParsedRouteParams($params);
$handler = $route->getHandler();
$controller = new $handler[0](Container::$request);
if ($controller instanceof SokoWeb\Interfaces\Authorization\ISecured) {
$authorized = $controller->authorize();
} else {
$authorized = true;
}
if (!$authorized) {
Container::$request->session()->set('redirect_after_login', $url);
$response = new Redirect(Container::$routeCollection->getRoute('login')->generateLink(), IRedirect::TEMPORARY);
header('Location: ' . $response->getUrl(), true, $response->getHttpCode());
return;
}
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']);
header('Content-Type: text/html; charset=UTF-8', true, 403);
$content->render();
return;
}
$response = call_user_func([$controller, $handler[1]]);
if ($response instanceof SokoWeb\Interfaces\Response\IContent) {
header('Content-Type: ' . $response->getContentType() . '; charset=UTF-8');
$response->render();
return;
} elseif ($response instanceof SokoWeb\Interfaces\Response\IRedirect) {
header('Location: ' . $response->getUrl(), true, $response->getHttpCode());
return;
}
}
$content = new SokoWeb\Response\HtmlContent('error/404');
header('Content-Type: text/html; charset=UTF-8', true, 404);
$content->render();
+57 -4
View File
@@ -88,10 +88,14 @@ sub {
}
hr {
border: solid #bbbbbb 1px;
border: solid #bbbbcc 1px;
margin: 10px 0;
}
.normal {
font-weight: 400;
}
.bold {
font-weight: 700;
}
@@ -109,7 +113,7 @@ p.small, span.small {
}
.marginLeft {
margin-left: 10px;
margin-left: 5px;
}
.marginBottom {
@@ -117,7 +121,7 @@ p.small, span.small {
}
.marginRight {
margin-right: 10px;
margin-right: 5px;
}
.center {
@@ -418,13 +422,61 @@ div.buttonContainer>button {
div.box {
width: 576px;
background-color: #eeeeee;
background-color: #eeeef4;
border-radius: 3px;
margin: 10px auto;
padding: 10px;
box-sizing: border-box;
}
div.gridContainer {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
grid-gap: 10px;
}
div.gridContainer > div {
background-color: #eeeef4;
border-radius: 3px;
padding: 5px 10px;
}
table {
border-collapse: separate;
border-spacing: 0;
}
table.fullWidth {
width: 100%;
}
table th {
font-weight: bold;
}
table th, table td {
padding: 3px 0;
vertical-align: middle;
}
table th:not(:first-child), table td:not(:first-child) {
padding-left: 3px;
}
table th:not(:last-child), table td:not(:last-child) {
padding-right: 3px;
}
.choices__inner {
box-sizing: border-box;
}
@media screen and (max-width: 424px) {
div.gridContainer {
grid-template-columns: auto;
}
}
@media screen and (max-width: 599px) {
header h1 span {
display: none;
@@ -433,6 +485,7 @@ div.box {
margin-top: 4px;
}
button, a.button {
margin: 3px 0;
padding: 0;
width: 100%;
}
+1 -1
View File
@@ -3,7 +3,7 @@ var Account = {
countdown: null,
openGoogleAuthenticate: function () {
window.open('/account/googleAuthenticate', 'googleAuthenticate', 'height=600,width=600')
window.open(googleAuthenticateUrl, 'googleAuthenticate', 'height=600,width=600')
},
authenticatedWithGoogleCallback: function (authenticatedWithGoogleUntil) {
@@ -0,0 +1,17 @@
(function () {
const element = document.getElementById('newMember').elements['user_id'];
const choices = new Choices(element, {
noResultsText: 'No users found',
noChoicesText: 'Start typing to search users'
});
element.addEventListener('search', RVR.debounce(async function (e) {
RVR.httpRequest('GET', searchUserUrl.replace('QUERY', encodeURIComponent(e.detail.value)), function () {
choices.setChoices(this.response.results, 'value', 'label', true);
});
}));
element.addEventListener('choice', function () {
choices.setChoices([], 'value', 'label', true);
});
})();
+17 -3
View File
@@ -49,7 +49,7 @@ var RVR = {
document.getElementById('loading').style.visibility = 'visible';
var formData = new FormData(form);
var formError = form.getElementsByClassName('formError')[0];
var formError = document.getElementsByClassName('formError')[0];
var pageLeaveOnSuccess = form.dataset.redirectOnSuccess || form.dataset.reloadOnSuccess;
RVR.httpRequest('POST', form.action, function () {
@@ -163,8 +163,14 @@ var RVR = {
for (var i = 0; i < observedInputs.length; i++) {
var input = form.elements[observedInputs[i]];
if (input.defaultValue !== input.value) {
anyChanged = true;
if (input.type === 'checkbox') {
if (input.defaultChecked !== input.checked) {
anyChanged = true;
}
} else {
if (input.defaultValue !== input.value) {
anyChanged = true;
}
}
}
@@ -193,6 +199,14 @@ var RVR = {
form.onreset = function () {
form.elements.submit.disabled = true;
}
},
debounce: function(func, timeout = 300) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, timeout);
};
}
};
+57
View File
@@ -0,0 +1,57 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"@babel/runtime": {
"version": "7.21.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz",
"integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==",
"requires": {
"regenerator-runtime": "^0.13.11"
}
},
"choices.js": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/choices.js/-/choices.js-10.2.0.tgz",
"integrity": "sha512-8PKy6wq7BMjNwDTZwr3+Zry6G2+opJaAJDDA/j3yxvqSCnvkKe7ZIFfIyOhoc7htIWFhsfzF9tJpGUATcpUtPg==",
"requires": {
"deepmerge": "^4.2.2",
"fuse.js": "^6.6.2",
"redux": "^4.2.0"
}
},
"deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="
},
"fuse.js": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.6.2.tgz",
"integrity": "sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA=="
},
"leaflet": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.3.tgz",
"integrity": "sha512-iB2cR9vAkDOu5l3HAay2obcUHZ7xwUBBjph8+PGtmW/2lYhbLizWtG7nTeYht36WfOslixQF9D/uSIzhZgGMfQ=="
},
"leaflet.markercluster": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/leaflet.markercluster/-/leaflet.markercluster-1.5.3.tgz",
"integrity": "sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA=="
},
"redux": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
"requires": {
"@babel/runtime": "^7.9.2"
}
},
"regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
}
}
}
+2 -1
View File
@@ -1,6 +1,7 @@
{
"dependencies": {
"leaflet": "^1.6.0",
"leaflet.markercluster": "^1.4.1"
"leaflet.markercluster": "^1.4.1",
"choices.js": "^10.2.0"
}
}
+3
View File
@@ -9,5 +9,8 @@ $app->add(new RVR\Cli\MigrateDatabaseCommand());
$app->add(new RVR\Cli\AddUserCommand());
$app->add(new RVR\Cli\LinkViewCommand());
$app->add(new RVR\Cli\MaintainDatabaseCommand());
$app->add(new RVR\Cli\AddOAuthClientCommand());
$app->add(new RVR\Cli\AddOAuthRedirectUriCommand());
$app->add(new RVR\Cli\RemoveOAuthRedirectUriCommand());
$app->run();
+1 -1
View File
@@ -7,7 +7,7 @@ if [[ "${BRANCH_NAME}" =~ $BRANCH_PATTERN ]]; then
TICKET_ID=$(echo $BRANCH_NAME | sed -E "s@$BRANCH_PATTERN@\\2@")
COMMIT_MESSAGE=$(head -n 1 $1)
COMMIT_MESSAGE_REGEX="^$TICKET_ID .*"
COMMIT_MESSAGE_REGEX="^(fixup! )?$TICKET_ID .*"
if [[ ! "${COMMIT_MESSAGE}" =~ $COMMIT_MESSAGE_REGEX ]]; then
sed -i.bak -e "1s/^/$TICKET_ID /" $1
+52
View File
@@ -0,0 +1,52 @@
<?php namespace RVR\Cli;
use DateTime;
use RVR\PersistentData\Model\OAuthClient;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class AddOAuthClientCommand extends Command
{
public function configure(): void
{
$this->setName('oauth:add-client')
->setDescription('Adding of OAuth client.')
->addArgument('client-id', InputArgument::OPTIONAL, 'Client ID')
->addArgument('preapproved', InputArgument::OPTIONAL, 'Preapproved');
}
public function execute(InputInterface $input, OutputInterface $output): int
{
$clientId = $input->getArgument('client-id') ? $input->getArgument('client-id') : bin2hex(random_bytes(8));
$clientSecret = bin2hex(random_bytes(20));
$oAuthClient = new OAuthClient();
$oAuthClient->setClientId($clientId);
$oAuthClient->setClientSecret($clientSecret);
$oAuthClient->setCreatedDate(new DateTime());
if ($input->getArgument('preapproved')) {
$oAuthClient->setPreapproved($input->getArgument('preapproved'));
}
try {
\Container::$persistentDataManager->saveToDb($oAuthClient);
} catch (\Exception $e) {
$output->writeln('<error>Adding OAuth client failed!</error>');
$output->writeln('');
$output->writeln((string) $e);
$output->writeln('');
return 1;
}
$output->writeln('<info>OAuth client was successfully added!</info>');
$output->writeln('<info>Client ID: ' . $clientId . '</info>');
$output->writeln('<info>Client secret: ' . $clientSecret . '</info>');
return 0;
}
}
+52
View File
@@ -0,0 +1,52 @@
<?php namespace RVR\Cli;
use RVR\Repository\OAuthClientRepository;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class AddOAuthRedirectUriCommand extends Command
{
public function configure(): void
{
$this->setName('oauth:add-redirect-uri')
->setDescription('Adding of redirect URI for OAuth client.')
->addArgument('client_id', InputArgument::REQUIRED, 'The OAuth client ID')
->addArgument('redirect_uris', InputArgument::IS_ARRAY, 'Redirect URIs to add');
}
public function execute(InputInterface $input, OutputInterface $output): int
{
$oAuthClientRepository = new OAuthClientRepository();
$oAuthClient = $oAuthClientRepository->getByClientId($input->getArgument('client_id'));
if ($oAuthClient === null) {
$output->writeln('<error>OAuth client does not exist!</error>');
return 1;
}
$redirectUris = array_unique(array_merge($oAuthClient->getRedirectUrisArray(), $input->getArgument('redirect_uris')));
$oAuthClient->setRedirectUrisArray($redirectUris);
try {
\Container::$persistentDataManager->saveToDb($oAuthClient);
} catch (\Exception $e) {
$output->writeln('<error>Adding redirect URI failed!</error>');
$output->writeln('');
$output->writeln((string) $e);
$output->writeln('');
return 1;
}
$redirectUrisToPrint = [];
foreach ($redirectUris as $redirectUri) $redirectUrisToPrint[] = '* ' . $redirectUri;
$output->writeln('<info>Redirect URIS were successfully added! Current URIs:' . "\n" . implode("\n", $redirectUrisToPrint) . '</info>');
return 0;
}
}
+1 -3
View File
@@ -1,7 +1,6 @@
<?php namespace RVR\Cli;
use DateTime;
use SokoWeb\PersistentData\PersistentDataManager;
use RVR\PersistentData\Model\User;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
@@ -36,8 +35,7 @@ class AddUserCommand extends Command
}
try {
$pdm = new PersistentDataManager();
$pdm->saveToDb($user);
\Container::$persistentDataManager->saveToDb($user);
} catch (\Exception $e) {
$output->writeln('<error>Adding user failed!</error>');
$output->writeln('');
+1 -5
View File
@@ -4,7 +4,6 @@ use DateTime;
use SokoWeb\Database\Query\Modify;
use SokoWeb\Database\Query\Select;
use SokoWeb\Interfaces\Database\IResultSet;
use SokoWeb\PersistentData\PersistentDataManager;
use RVR\Repository\UserPasswordResetterRepository;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -12,15 +11,12 @@ use Symfony\Component\Console\Output\OutputInterface;
class MaintainDatabaseCommand extends Command
{
private PersistentDataManager $pdm;
private UserPasswordResetterRepository $userPasswordResetterRepository;
public function __construct()
{
parent::__construct();
$this->pdm = new PersistentDataManager();
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
}
@@ -54,7 +50,7 @@ class MaintainDatabaseCommand extends Command
private function deleteExpiredPasswordResetters(): void
{
foreach ($this->userPasswordResetterRepository->getAllExpired() as $passwordResetter) {
$this->pdm->deleteFromDb($passwordResetter);
\Container::$persistentDataManager->deleteFromDb($passwordResetter);
}
}
+52
View File
@@ -0,0 +1,52 @@
<?php namespace RVR\Cli;
use RVR\Repository\OAuthClientRepository;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class RemoveOAuthRedirectUriCommand extends Command
{
public function configure(): void
{
$this->setName('oauth:remove-redirect-uri')
->setDescription('Removing of redirect URI for OAuth client.')
->addArgument('client_id', InputArgument::REQUIRED, 'The OAuth client ID')
->addArgument('redirect_uris', InputArgument::IS_ARRAY, 'Redirect URIs to remove');
}
public function execute(InputInterface $input, OutputInterface $output): int
{
$oAuthClientRepository = new OAuthClientRepository();
$oAuthClient = $oAuthClientRepository->getByClientId($input->getArgument('client_id'));
if ($oAuthClient === null) {
$output->writeln('<error>OAuth client does not exist!</error>');
return 1;
}
$redirectUris = array_diff($oAuthClient->getRedirectUrisArray(), $input->getArgument('redirect_uris'));
$oAuthClient->setRedirectUrisArray($redirectUris);
try {
\Container::$persistentDataManager->saveToDb($oAuthClient);
} catch (\Exception $e) {
$output->writeln('<error>Removing redirect URI failed!</error>');
$output->writeln('');
$output->writeln((string) $e);
$output->writeln('');
return 1;
}
$redirectUrisToPrint = [];
foreach ($redirectUris as $redirectUri) $redirectUrisToPrint[] = '* ' . $redirectUri;
$output->writeln('<info>Redirect URIS were successfully removed! Current URIs:' . "\n" . implode("\n", $redirectUrisToPrint) . '</info>');
return 0;
}
}
+347
View File
@@ -0,0 +1,347 @@
<?php namespace RVR\Controller;
use DateTime;
use RVR\PersistentData\Model\Community;
use RVR\PersistentData\Model\CommunityMember;
use RVR\PersistentData\Model\Currency;
use RVR\PersistentData\Model\CurrencyExchangeRate;
use RVR\PersistentData\Model\User;
use RVR\Repository\CommunityRepository;
use RVR\Repository\CommunityMemberRepository;
use RVR\Repository\CurrencyExchangeRateRepository;
use RVR\Repository\CurrencyRepository;
use RVR\Repository\UserRepository;
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
use SokoWeb\Interfaces\Response\IContent;
use SokoWeb\Response\HtmlContent;
use SokoWeb\Response\JsonContent;
class CommunityController implements IAuthenticationRequired
{
private UserRepository $userRepository;
private CommunityRepository $communityRepository;
private CommunityMemberRepository $communityMemberRepository;
private CurrencyRepository $currencyRepository;
private CurrencyExchangeRateRepository $currencyExchangeRatesRepository;
public function __construct()
{
$this->userRepository = new UserRepository();
$this->communityRepository = new CommunityRepository();
$this->communityMemberRepository = new CommunityMemberRepository();
$this->currencyRepository = new CurrencyRepository();
$this->currencyExchangeRatesRepository = new CurrencyExchangeRateRepository();
}
public function isAuthenticationRequired(): bool
{
return true;
}
public function getCommunityHome(): ?IContent
{
if (!$this->checkPermission(\Container::$request->query('communityId'), false, $community, $ownCommunityMember)) {
return null;
}
return new HtmlContent('communities/community', [
'community' => $community,
'members' => $this->getMembers($community),
'currencies' => $this->getCurrencies($community),
'upcomingEvents' => [],
'editPermission' => $ownCommunityMember->getOwner()
]);
}
public function getCommunityNew(): IContent
{
return new HtmlContent('communities/community_edit');
}
public function getCommunityEdit(): ?IContent
{
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
return null;
}
return new HtmlContent('communities/community_edit', [
'community' => $community
]);
}
public function getMembersEdit(): ?IContent
{
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
return null;
}
return new HtmlContent('communities/community_members', [
'community' => $community,
'members' => $this->getMembers($community)
]);
}
public function saveMember(): ?IContent
{
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
return null;
}
$communityMemberId = \Container::$request->query('community_member_id');
if ($communityMemberId) {
$communityMember = $this->communityMemberRepository->getById($communityMemberId);
if ($communityMember->getUserId() === $ownCommunityMember->getUserId()) {
return new JsonContent([
'error' => ['errorText' => 'Own user cannot be edited.']
]);
}
} else {
$user = $this->userRepository->getById(\Container::$request->post('user_id'));
$communityMember = new CommunityMember();
$communityMember->setCommunity($community);
$communityMember->setUser($user);
}
$communityMember->setOwner((bool)\Container::$request->post('owner'));
\Container::$persistentDataManager->saveToDb($communityMember);
return new JsonContent(['success' => true]);
}
public function deleteMember(): ?IContent
{
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
return null;
}
$communityMember = $this->communityMemberRepository->getById(\Container::$request->query('community_member_id'));
if ($communityMember->getUserId() === \Container::$request->user()->getUniqueId()) {
return new JsonContent([
'error' => ['errorText' => 'Own user cannot be deleted.']
]);
}
\Container::$persistentDataManager->deleteFromDb($communityMember);
return new JsonContent(['success' => true]);
}
public function getCurrenciesEdit(): ?IContent
{
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
return null;
}
return new HtmlContent('communities/community_currencies', [
'community' => $community,
'currencies' => $this->getCurrencies($community)
]);
}
public function saveCurrency(): ?IContent
{
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
return null;
}
$code = \Container::$request->post('code');
$roundDigits = (int)\Container::$request->post('round_digits');
if (strlen($code) === 0 || strlen($code) > 3 || $roundDigits < 0 || $roundDigits > 9) {
return new JsonContent([
'error' => ['errorText' => 'Please fill all required fields!']
]);
}
$currencyId = \Container::$request->query('currency_id');
if ($currencyId){
$currency = $this->currencyRepository->getById($currencyId);
} else {
$currency = new Currency();
$currency->setCommunity($community);
}
$existingCurrency = $this->currencyRepository->getByCommunityAndCurrencyCode($community, $code);
if ($existingCurrency !== null && $currency->getId() !== $existingCurrency->getId()) {
return new JsonContent([
'error' => ['errorText' => 'A currency with the same code exists for this community.']
]);
}
$currency->setCode($code);
$currency->setRoundDigits($roundDigits);
\Container::$persistentDataManager->saveToDb($currency);
return new JsonContent(['success' => true]);
}
public function deleteCurrency(): ?IContent
{
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
return null;
}
$currency = $this->currencyRepository->getById(\Container::$request->query('currency_id'));
\Container::$persistentDataManager->deleteFromDb($currency);
return new JsonContent(['success' => true]);
}
public function getCurrencyExchangeRates(): ?IContent
{
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
return null;
}
$currency = $this->currencyRepository->getByCommunityAndCurrencyCode($community, \Container::$request->query('code'));
if ($currency === null) {
return null;
}
$currencyExchangeRates = $this->currencyExchangeRatesRepository->getAllByCurrency($currency);
return new HtmlContent('communities/currency_exchange_rates', [
'community' => $community,
'currency' => $currency,
'currencyExchangeRates' => $currencyExchangeRates,
'editPermission' => $ownCommunityMember->getOwner()
]);
}
public function saveCurrencyExchangeRate(): ?IContent
{
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
return null;
}
$currency = $this->currencyRepository->getByCommunityAndCurrencyCode($community, \Container::$request->query('code'));
if ($currency === null) {
return null;
}
$exchangeRate = (float)\Container::$request->post('exchange_rate');
if ($exchangeRate < 0) {
return new JsonContent([
'error' => ['errorText' => 'Please fill all required fields!']
]);
}
$currencyExchangeRateId = \Container::$request->query('currency_exchange_rate_id');
if ($currencyExchangeRateId){
$currencyExchangeRate = $this->currencyExchangeRatesRepository->getById($currencyExchangeRateId);
} else {
$currencyExchangeRate = new CurrencyExchangeRate();
$currencyExchangeRate->setCurrency($currency);
}
$currencyExchangeRate->setExchangeRate($exchangeRate);
$currencyExchangeRate->setValidFromDate(new DateTime(\Container::$request->post('valid_from')));
\Container::$persistentDataManager->saveToDb($currencyExchangeRate);
return new JsonContent(['success' => true]);
}
public function deleteCurrencyExchangeRate(): ?IContent
{
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
return null;
}
$currency = $this->currencyRepository->getByCommunityAndCurrencyCode($community, \Container::$request->query('code'));
if ($currency === null) {
return null;
}
$currencyExchangeRate = $this->currencyExchangeRatesRepository->getById(\Container::$request->query('currency_exchange_rate_id'));
\Container::$persistentDataManager->deleteFromDb($currencyExchangeRate);
return new JsonContent(['success' => true]);
}
public function saveCommunity(): ?IContent
{
$name = \Container::$request->post('name');
$currency = \Container::$request->post('currency');
if (strlen($name) === 0 || strlen($currency) === 0 || strlen($currency) > 3) {
return new JsonContent([
'error' => ['errorText' => 'Please fill all required fields!']
]);
}
$communityId = \Container::$request->query('communityId');
if ($communityId){
if (!$this->checkPermission($communityId, true, $community, $ownCommunityMember)) {
return null;
}
} else {
$community = new Community();
$community->setCreatedDate(new DateTime());
}
$community->setName($name);
$community->setCurrency($currency);
\Container::$persistentDataManager->saveToDb($community);
if (!$communityId) {
/**
* @var User $user
*/
$user = \Container::$request->user();
$communityMember = new CommunityMember();
$communityMember->setCommunity($community);
$communityMember->setUser($user);
$communityMember->setOwner(true);
\Container::$persistentDataManager->saveToDb($communityMember);
}
return new JsonContent([
'redirect' => ['target' => \Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()])]
]);
}
private function getMembers(Community $community): array
{
$members = iterator_to_array($this->communityMemberRepository->getAllByCommunity($community, true));
usort($members, function($a, $b) {
return strnatcmp($a->getUser()->getDisplayName(), $b->getUser()->getDisplayName());
});
return $members;
}
private function getCurrencies(Community $community): array
{
$currencies = iterator_to_array($this->currencyRepository->getAllByCommunity($community));
usort($currencies, function($a, $b) {
return strnatcmp($a->getCode(), $b->getCode());
});
return $currencies;
}
private function checkPermission(
int $communityId,
bool $needToBeOwner,
?Community &$community,
?CommunityMember &$ownCommunityMember): bool
{
$community = $this->communityRepository->getById($communityId);
if ($community === null) {
return false;
}
/**
* @var User $user
*/
$user = \Container::$request->user();
$ownCommunityMember = $this->communityMemberRepository->getByCommunityAndUser($community, $user);
if ($ownCommunityMember === null || ($needToBeOwner && !$ownCommunityMember->getOwner())) {
return false;
}
return true;
}
}
+28 -11
View File
@@ -1,27 +1,44 @@
<?php namespace RVR\Controller;
use SokoWeb\Interfaces\Authorization\ISecured;
use SokoWeb\Interfaces\Request\IRequest;
use RVR\PersistentData\Model\User;
use RVR\Repository\CommunityMemberRepository;
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
use SokoWeb\Interfaces\Response\IContent;
use SokoWeb\Response\HtmlContent;
use SokoWeb\Response\JsonContent;
class HomeController implements ISecured
class HomeController implements IAuthenticationRequired
{
private IRequest $request;
private CommunityMemberRepository $communityMemberRepository;
public function __construct(IRequest $request)
public function __construct()
{
$this->request = $request;
$this->communityMemberRepository = new CommunityMemberRepository();
}
public function authorize(): bool
public function isAuthenticationRequired(): bool
{
return $this->request->user() !== null;
return true;
}
public function getIndex(): IContent
public function getHome(): IContent
{
return new HtmlContent('index');
/**
* @var User $user
*/
$user = \Container::$request->user();
$ownCommunityMembers = $this->communityMemberRepository->getAllByUser($user, true);
$communities = [];
foreach ($ownCommunityMembers as $ownCommunityMember) {
$communities[] = $ownCommunityMember->getCommunity();
}
usort($communities, function($a, $b) {
return strnatcmp($a->getName(), $b->getName());
});
return new HtmlContent('home', [
'communities' => $communities,
'upcomingEvents' => []
]);
}
}
+48 -64
View File
@@ -2,13 +2,11 @@
use DateTime;
use SokoWeb\Http\Request;
use SokoWeb\Interfaces\Request\IRequest;
use SokoWeb\Interfaces\Response\IContent;
use SokoWeb\Interfaces\Response\IRedirect;
use SokoWeb\Mailing\Mail;
use SokoWeb\OAuth\GoogleOAuth;
use RVR\PersistentData\Model\UserPasswordResetter;
use SokoWeb\PersistentData\PersistentDataManager;
use RVR\Repository\UserPasswordResetterRepository;
use RVR\Repository\UserRepository;
use SokoWeb\Response\HtmlContent;
@@ -19,35 +17,29 @@ use SokoWeb\Util\JwtParser;
class LoginController
{
private IRequest $request;
private PersistentDataManager $pdm;
private UserRepository $userRepository;
private UserPasswordResetterRepository $userPasswordResetterRepository;
private string $redirectUrl;
public function __construct(IRequest $request)
public function __construct()
{
$this->request = $request;
$this->pdm = new PersistentDataManager();
$this->userRepository = new UserRepository();
$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();
$this->redirectUrl = \Container::$request->session()->has('redirect_after_login') ?
\Container::$request->session()->get('redirect_after_login') :
\Container::$routeCollection->getRoute('home')->generateLink();
}
public function getLoginForm()
{
if ($this->request->user() !== null) {
if (\Container::$request->user() !== null) {
$this->deleteRedirectUrl();
return new Redirect($this->redirectUrl, IRedirect::TEMPORARY);
}
return new HtmlContent('login/login', ['redirectUrl' => '/' . $this->redirectUrl]);
return new HtmlContent('login/login', ['redirectUrl' => $this->redirectUrl]);
}
public function getGoogleLoginRedirect(): IRedirect
@@ -55,13 +47,13 @@ class LoginController
$state = bin2hex(random_bytes(16));
$nonce = bin2hex(random_bytes(16));
$this->request->session()->set('oauth_state', $state);
$this->request->session()->set('oauth_nonce', $nonce);
\Container::$request->session()->set('oauth_state', $state);
\Container::$request->session()->set('oauth_nonce', $nonce);
$oAuth = new GoogleOAuth(new Request());
$url = $oAuth->getDialogUrl(
$state,
$this->request->getBase() . '/' . \Container::$routeCollection->getRoute('login-google-action')->generateLink(),
\Container::$request->getBase() . \Container::$routeCollection->getRoute('login-google-action')->generateLink(),
$nonce
);
@@ -70,12 +62,12 @@ class LoginController
public function getRequestPasswordResetForm()
{
if ($this->request->user() !== null) {
if (\Container::$request->user() !== null) {
$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' => \Container::$request->query('email')]);
}
public function getRequestPasswordResetSuccess(): IContent
@@ -85,12 +77,12 @@ class LoginController
public function getResetPasswordForm()
{
if ($this->request->user() !== null) {
if (\Container::$request->user() !== null) {
$this->deleteRedirectUrl();
return new Redirect($this->redirectUrl, IRedirect::TEMPORARY);
}
$token = $this->request->query('token');
$token = \Container::$request->query('token');
$resetter = $this->userPasswordResetterRepository->getByToken($token);
if ($resetter === null || $resetter->getExpiresDate() < new DateTime()) {
@@ -99,27 +91,27 @@ class LoginController
$user = $this->userRepository->getById($resetter->getUserId());
return new HtmlContent('login/reset_password', ['success' => true, 'token' => $token, 'email' => $user->getEmail(), 'redirectUrl' => '/' . $this->redirectUrl]);
return new HtmlContent('login/reset_password', ['success' => true, 'token' => $token, 'email' => $user->getEmail(), 'redirectUrl' => $this->redirectUrl]);
}
public function login(): IContent
{
if ($this->request->user() !== null) {
if (\Container::$request->user() !== null) {
$this->deleteRedirectUrl();
return new JsonContent(['success' => true]);
}
$user = $this->userRepository->getByEmailOrUsername($this->request->post('email'));
if ($user === null || !$user->checkPassword($this->request->post('password'))) {
$user = $this->userRepository->getByEmailOrUsername(\Container::$request->post('email'));
if ($user === null || !$user->checkPassword(\Container::$request->post('password'))) {
return new JsonContent([
'error' => [
'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>!'
'errorText' => 'No user found with the given email address / username or the given password is wrong. You can <a href="' .
\Container::$routeCollection->getRoute('password-requestReset')->generateLink(['email' => \Container::$request->post('email')]) . '" title="Request password reset">request password reset</a>!'
]
]);
}
$this->request->setUser($user);
\Container::$request->setUser($user);
$this->deleteRedirectUrl();
return new JsonContent(['success' => true]);
@@ -127,21 +119,21 @@ class LoginController
public function loginWithGoogle()
{
$defaultError = 'Authentication with Google failed. Please <a href="/login/google" title="Login with Google">try again</a>!';
$defaultError = 'Authentication with Google failed. Please <a href="' . \Container::$routeCollection->getRoute('login-google')->generateLink() . '" title="Login with Google">try again</a>!';
if ($this->request->user() !== null) {
if (\Container::$request->user() !== null) {
$this->deleteRedirectUrl();
return new Redirect($this->redirectUrl, IRedirect::TEMPORARY);
}
if ($this->request->query('state') !== $this->request->session()->get('oauth_state')) {
if (\Container::$request->query('state') !== \Container::$request->session()->get('oauth_state')) {
return new HtmlContent('login/google_login_error', ['error' => $defaultError]);
}
$oAuth = new GoogleOAuth(new Request());
$tokenData = $oAuth->getToken(
$this->request->query('code'),
$this->request->getBase() . '/' . \Container::$routeCollection->getRoute('login-google-action')->generateLink()
\Container::$request->query('code'),
\Container::$request->getBase() . \Container::$routeCollection->getRoute('login-google-action')->generateLink()
);
if (!isset($tokenData['id_token'])) {
@@ -151,7 +143,7 @@ class LoginController
$jwtParser = new JwtParser($tokenData['id_token']);
$idToken = $jwtParser->getPayload();
if ($idToken['nonce'] !== $this->request->session()->get('oauth_nonce')) {
if ($idToken['nonce'] !== \Container::$request->session()->get('oauth_nonce')) {
return new HtmlContent('login/google_login_error', ['error' => $defaultError]);
}
@@ -164,7 +156,7 @@ class LoginController
return new HtmlContent('login/google_login_error', ['error' => 'No user found for this Google account.']);
}
$this->request->setUser($user);
\Container::$request->setUser($user);
$this->deleteRedirectUrl();
return new Redirect($this->redirectUrl, IRedirect::TEMPORARY);
@@ -172,35 +164,35 @@ class LoginController
public function logout(): IRedirect
{
$this->request->setUser(null);
\Container::$request->setUser(null);
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
return new Redirect(\Container::$routeCollection->getRoute('home')->generateLink(), IRedirect::TEMPORARY);
}
public function requestPasswordReset(): IContent
{
if ($this->request->user() !== null) {
if (\Container::$request->user() !== null) {
$this->deleteRedirectUrl();
return new JsonContent([
'redirect' => [
'target' => '/' . $this->redirectUrl
'target' => $this->redirectUrl
]
]);
}
if (!empty($_ENV['RECAPTCHA_SITEKEY'])) {
if (!$this->request->post('g-recaptcha-response')) {
if (!\Container::$request->post('g-recaptcha-response')) {
return new JsonContent(['error' => ['errorText' => 'Please check "I\'m not a robot" in the reCAPTCHA box!']]);
}
$captchaValidator = new CaptchaValidator();
$captchaResponse = $captchaValidator->validate($this->request->post('g-recaptcha-response'));
$captchaResponse = $captchaValidator->validate(\Container::$request->post('g-recaptcha-response'));
if (!$captchaResponse['success']) {
return new JsonContent(['error' => ['errorText' => 'reCAPTCHA challenge failed. Please try again!']]);
}
}
$user = $this->userRepository->getByEmailOrUsername($this->request->post('email'));
$user = $this->userRepository->getByEmailOrUsername(\Container::$request->post('email'));
if ($user === null) {
return new JsonContent([
'error' => [
@@ -226,15 +218,11 @@ class LoginController
$passwordResetter->setToken($token);
$passwordResetter->setExpiresDate($expires);
\Container::$dbConnection->startTransaction();
if ($existingResetter !== null) {
$this->pdm->deleteFromDb($existingResetter);
\Container::$persistentDataManager->deleteFromDb($existingResetter);
}
$this->pdm->saveToDb($passwordResetter);
\Container::$dbConnection->commit();
\Container::$persistentDataManager->saveToDb($passwordResetter);
$this->sendPasswordResetEmail($user->getEmail(), $token, $expires);
@@ -243,27 +231,27 @@ class LoginController
public function resetPassword(): IContent
{
if ($this->request->user() !== null) {
if (\Container::$request->user() !== null) {
$this->deleteRedirectUrl();
return new JsonContent([
'redirect' => [
'target' => '/' . $this->redirectUrl
'target' => $this->redirectUrl
]
]);
}
$token = $this->request->query('token');
$token = \Container::$request->query('token');
$resetter = $this->userPasswordResetterRepository->getByToken($token);
if ($resetter === null || $resetter->getExpiresDate() < new DateTime()) {
return new JsonContent([
'redirect' => [
'target' => '/' . \Container::$routeCollection->getRoute('password-reset')->generateLink(['token' => $token])
'target' => \Container::$routeCollection->getRoute('password-reset')->generateLink(['token' => $token])
]
]);
}
if (strlen($this->request->post('password')) < 6) {
if (strlen(\Container::$request->post('password')) < 6) {
return new JsonContent([
'error' => [
'errorText' => 'The given password is too short. Please choose a password that is at least 6 characters long!'
@@ -271,22 +259,18 @@ class LoginController
]);
}
if ($this->request->post('password') !== $this->request->post('password_confirm')) {
if (\Container::$request->post('password') !== \Container::$request->post('password_confirm')) {
return new JsonContent(['error' => ['errorText' => 'The given passwords do not match.']]);
}
\Container::$dbConnection->startTransaction();
$this->pdm->deleteFromDb($resetter);
\Container::$persistentDataManager->deleteFromDb($resetter);
$user = $this->userRepository->getById($resetter->getUserId());
$user->setPlainPassword($this->request->post('password'));
$user->setPlainPassword(\Container::$request->post('password'));
$this->pdm->saveToDb($user);
\Container::$persistentDataManager->saveToDb($user);
\Container::$dbConnection->commit();
$this->request->setUser($user);
\Container::$request->setUser($user);
$this->deleteRedirectUrl();
return new JsonContent(['success' => true]);
@@ -299,7 +283,7 @@ class LoginController
$mail->setSubject($_ENV['APP_NAME'] . ' - Password reset');
$mail->setBodyFromTemplate('password-reset', [
'EMAIL' => $email,
'RESET_LINK' => $this->request->getBase() . '/' .
'RESET_LINK' => \Container::$request->getBase() .
\Container::$routeCollection->getRoute('password-reset')->generateLink(['token' => $token]),
'EXPIRES' => $expires->format('Y-m-d H:i T')
]);
@@ -308,6 +292,6 @@ class LoginController
private function deleteRedirectUrl(): void
{
$this->request->session()->delete('redirect_after_login');
\Container::$request->session()->delete('redirect_after_login');
}
}
+79
View File
@@ -0,0 +1,79 @@
<?php namespace RVR\Controller;
use DateTime;
use RVR\PersistentData\Model\OAuthToken;
use RVR\PersistentData\Model\User;
use RVR\Repository\OAuthClientRepository;
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
use SokoWeb\Interfaces\Response\IRedirect;
use SokoWeb\Response\Redirect;
use SokoWeb\Response\HtmlContent;
class OAuthAuthController implements IAuthenticationRequired
{
private OAuthClientRepository $oAuthClientRepository;
public function __construct()
{
$this->oAuthClientRepository = new OAuthClientRepository();
}
public function isAuthenticationRequired(): bool
{
return true;
}
public function auth()
{
$redirectUri = \Container::$request->query('redirect_uri');
$clientId = \Container::$request->query('client_id');
$scope = \Container::$request->query('scope') ? \Container::$request->query('scope'): '';
$state = \Container::$request->query('state');
$nonce = \Container::$request->query('nonce') ? \Container::$request->query('nonce'): '';
if (!$clientId || !$redirectUri || !$state) {
return new HtmlContent('oauth/oauth_error', ['error' => 'An invalid request was made. Please start authentication again.']);
}
$client = $this->oAuthClientRepository->getByClientId($clientId);
if ($client === null) {
return new HtmlContent('oauth/oauth_error', ['error' => 'Client is not authorized.']);
}
$redirectUriParsed = parse_url($redirectUri);
$redirectUriBase = $redirectUriParsed['scheme'] . '://' . $redirectUriParsed['host'] . $redirectUriParsed['path'];
$redirectUriQuery = [];
if (isset($redirectUriParsed['query'])) {
parse_str($redirectUriParsed['query'], $redirectUriQuery);
}
if (!in_array($redirectUriBase, $client->getRedirectUrisArray())) {
return new HtmlContent('oauth/oauth_error', ['error' => 'Redirect URI \'' . $redirectUriBase .'\' is not allowed for this client.']);
}
/**
* @var ?User $user
*/
$user = \Container::$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'));
\Container::$persistentDataManager->saveToDb($token);
$redirectUriQuery = array_merge($redirectUriQuery, [
'state' => $state,
'code' => $code
]);
$finalRedirectUri = $redirectUriBase . '?' . http_build_query($redirectUriQuery);
return new Redirect($finalRedirectUri, IRedirect::TEMPORARY);
}
}
+197
View File
@@ -0,0 +1,197 @@
<?php namespace RVR\Controller;
use DateTime;
use Firebase\JWT\JWT;
use RVR\Repository\OAuthTokenRepository;
use RVR\Repository\UserRepository;
use RVR\PersistentData\Model\User;
use RVR\Repository\OAuthClientRepository;
use SokoWeb\Interfaces\Response\IContent;
use SokoWeb\Response\JsonContent;
class OAuthController
{
private OAuthClientRepository $oAuthClientRepository;
private OAuthTokenRepository $oAuthTokenRepository;
private UserRepository $userRepository;
public function __construct()
{
$this->oAuthClientRepository = new OAuthClientRepository();
$this->oAuthTokenRepository = new OAuthTokenRepository();
$this->userRepository = new UserRepository();
}
public function getToken(): ?IContent
{
$clientId = \Container::$request->post('client_id');
$clientSecret = \Container::$request->post('client_secret');
$code = \Container::$request->post('code');
if (!$clientId || !$clientSecret || !$code) {
return new JsonContent([
'error' => 'An invalid request was made.'
]);
}
$client = $this->oAuthClientRepository->getByClientId($clientId);
if ($client === null || $client->getClientSecret() !== $clientSecret) {
return new JsonContent([
'error' => 'Client is not authorized.'
]);
}
$token = $this->oAuthTokenRepository->getByCode($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(),
'aud' => $clientId,
'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
{
$authorization = \Container::$request->header('Authorization');
if ($authorization === null) {
return new JsonContent([
'error' => 'No Authorization header was sent.'
]);
}
$accessToken = substr($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' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth-auth')->generateLink(),
'token_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth-token')->generateLink(),
'userinfo_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth-userinfo')->generateLink(),
'jwks_uri' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth-certs')->generateLink(),
'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;
}
}
-129
View File
@@ -1,129 +0,0 @@
<?php namespace RVR\Controller;
use DateTime;
use Firebase\JWT\JWT;
use RVR\PersistentData\Model\OAuthToken;
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\Interfaces\Response\IRedirect;
use SokoWeb\Response\Redirect;
use SokoWeb\PersistentData\PersistentDataManager;
use SokoWeb\Response\HtmlContent;
use SokoWeb\Response\JsonContent;
class OAuthLoginController
{
private IRequest $request;
private PersistentDataManager $pdm;
public function __construct(IRequest $request)
{
$this->request = $request;
$this->pdm = new PersistentDataManager();
}
public function startOauth()
{
$redirectUri = $this->request->query('redirect_uri');
$state = $this->request->query('state');
$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()->set('oauth_payload', [
'redirect_uri' => $redirectUri,
'state' => $state,
'nonce' => $nonce === null ? '' : $nonce
]);
$this->request->session()->set('redirect_after_login', \Container::$routeCollection->getRoute('oauth-finish')->generateLink());
return new Redirect(\Container::$routeCollection->getRoute('login')->generateLink(), IRedirect::TEMPORARY);
}
public function finishOauth()
{
$oAuthPayload = $this->request->session()->get('oauth_payload');
if ($oAuthPayload === null) {
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();
if ($user === null) {
return new HtmlContent('oauth/oauth_error', ['error' => 'You are not logged in. Please start authentication again.']);
}
$code = bin2hex(random_bytes(16));
$token = new OAuthToken();
$token->setNonce($oAuthPayload['nonce']);
$token->setUser($user);
$token->setCode($code);
$token->setCreatedDate(new DateTime());
$token->setExpiresDate(new DateTime('+5 minutes'));
$this->pdm->saveToDb($token);
$redirectUri = $oAuthPayload['redirect_uri'];
$additionalUriParams = [
'state' => $oAuthPayload['state'],
'code' => $code
];
$and = (strpos($redirectUri, '?') !== false) ? '&' : '?';
$finalRedirectUri = $redirectUri . $and . http_build_query($additionalUriParams);
return new Redirect($finalRedirectUri, IRedirect::TEMPORARY);
}
public function getToken(): ?IContent
{
$oAuthTokenRepository = new OAuthTokenRepository();
$userRepository = new UserRepository();
$token = $oAuthTokenRepository->getByCode($this->request->post('code'));
if ($token === null || $token->getExpiresDate() < new DateTime()) {
return new JsonContent([
'error' => 'The provided code is invalid.'
]);
}
$user = $userRepository->getById($token->getUserId());
$payload = [
'iss' => $_ENV['APP_URL'],
'iat' => (int)$token->getCreatedDate()->format('U'),
'nbf' => (int)$token->getCreatedDate()->format('U'),
'exp' => (int)$token->getExpiresDate()->format('U'),
'nonce' => $token->getNonce(),
'sub' => $user->getId(),
'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');
return new JsonContent([
'id_token' => $jwt
]);
}
public function getJwtPublicKey(): IContent
{
$publicKey = file_get_contents(ROOT . '/' . $_ENV['JWT_RSA_PUBLIC_KEY']);
return new JsonContent(['pubkey' => $publicKey]);
}
}
+28 -36
View File
@@ -2,12 +2,10 @@
use DateTime;
use SokoWeb\Http\Request;
use SokoWeb\Interfaces\Authorization\ISecured;
use SokoWeb\Interfaces\Request\IRequest;
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
use SokoWeb\Interfaces\Response\IContent;
use SokoWeb\Interfaces\Response\IRedirect;
use SokoWeb\OAuth\GoogleOAuth;
use SokoWeb\PersistentData\PersistentDataManager;
use RVR\PersistentData\Model\User;
use SokoWeb\Response\HtmlContent;
use SokoWeb\Response\JsonContent;
@@ -15,24 +13,18 @@ use SokoWeb\Response\Redirect;
use SokoWeb\Util\JwtParser;
use RVR\Repository\UserRepository;
class UserController implements ISecured
class UserController implements IAuthenticationRequired
{
private IRequest $request;
private PersistentDataManager $pdm;
private UserRepository $userRepository;
public function __construct(IRequest $request)
public function __construct()
{
$this->request = $request;
$this->pdm = new PersistentDataManager();
$this->userRepository = new UserRepository();
}
public function authorize(): bool
public function isAuthenticationRequired(): bool
{
return $this->request->user() !== null;
return true;
}
public function getAccount(): IContent
@@ -40,7 +32,7 @@ class UserController implements ISecured
/**
* @var User $user
*/
$user = $this->request->user();
$user = \Container::$request->user();
return new HtmlContent('account/account', ['user' => $user->toArray()]);
}
@@ -50,19 +42,19 @@ class UserController implements ISecured
/**
* @var User $user
*/
$user = $this->request->user();
$user = \Container::$request->user();
$state = bin2hex(random_bytes(16));
$nonce = bin2hex(random_bytes(16));
$this->request->session()->set('oauth_state', $state);
$this->request->session()->set('oauth_nonce', $nonce);
\Container::$request->session()->set('oauth_state', $state);
\Container::$request->session()->set('oauth_nonce', $nonce);
$oAuth = new GoogleOAuth(new Request());
$url = $oAuth->getDialogUrl(
$state,
$this->request->getBase() . '/' . \Container::$routeCollection->getRoute('account.googleAuthenticate-action')->generateLink(),
\Container::$request->getBase() . \Container::$routeCollection->getRoute('account.googleAuthenticate-action')->generateLink(),
$nonce,
$user->getEmail()
);
@@ -75,16 +67,16 @@ class UserController implements ISecured
/**
* @var User $user
*/
$user = $this->request->user();
$user = \Container::$request->user();
if ($this->request->query('state') !== $this->request->session()->get('oauth_state')) {
if (\Container::$request->query('state') !== \Container::$request->session()->get('oauth_state')) {
return new HtmlContent('account/google_authenticate', ['success' => false]);
}
$oAuth = new GoogleOAuth(new Request());
$tokenData = $oAuth->getToken(
$this->request->query('code'),
$this->request->getBase() . '/' . \Container::$routeCollection->getRoute('account.googleAuthenticate-action')->generateLink()
\Container::$request->query('code'),
\Container::$request->getBase() . \Container::$routeCollection->getRoute('account.googleAuthenticate-action')->generateLink()
);
if (!isset($tokenData['id_token'])) {
@@ -94,7 +86,7 @@ class UserController implements ISecured
$jwtParser = new JwtParser($tokenData['id_token']);
$idToken = $jwtParser->getPayload();
if ($idToken['nonce'] !== $this->request->session()->get('oauth_nonce')) {
if ($idToken['nonce'] !== \Container::$request->session()->get('oauth_nonce')) {
return new HtmlContent('account/google_authenticate', ['success' => false]);
}
@@ -106,7 +98,7 @@ class UserController implements ISecured
}
$authenticatedWithGoogleUntil = new DateTime('+45 seconds');
$this->request->session()->set('authenticated_with_google_until', $authenticatedWithGoogleUntil);
\Container::$request->session()->set('authenticated_with_google_until', $authenticatedWithGoogleUntil);
return new HtmlContent('account/google_authenticate', [
'success' => true,
@@ -119,18 +111,18 @@ class UserController implements ISecured
/**
* @var User $user
*/
$user = $this->request->user();
$user = \Container::$request->user();
if (!$this->confirmUserIdentity(
$user,
$this->request->session()->get('authenticated_with_google_until'),
$this->request->post('password'),
\Container::$request->session()->get('authenticated_with_google_until'),
\Container::$request->post('password'),
$error
)) {
return new JsonContent(['error' => ['errorText' => $error]]);
}
$newEmail = $this->request->post('email');
$newEmail = \Container::$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.']]);
@@ -143,7 +135,7 @@ class UserController implements ISecured
$user->setEmail($newEmail);
}
$newUsername = $this->request->post('username');
$newUsername = \Container::$request->post('username');
if ($newUsername !== $user->getUsername()) {
if (strlen($newUsername) > 0) {
if (filter_var($newUsername, FILTER_VALIDATE_EMAIL)) {
@@ -160,7 +152,7 @@ class UserController implements ISecured
}
}
$newPassword = $this->request->post('password_new');
$newPassword = \Container::$request->post('password_new');
if (strlen($newPassword) > 0) {
if (strlen($newPassword) < 6) {
return new JsonContent([
@@ -170,7 +162,7 @@ class UserController implements ISecured
]);
}
if ($newPassword !== $this->request->post('password_new_confirm')) {
if ($newPassword !== \Container::$request->post('password_new_confirm')) {
return new JsonContent([
'error' => [
'errorText' => 'The given new passwords do not match.'
@@ -181,13 +173,13 @@ class UserController implements ISecured
$user->setPlainPassword($newPassword);
}
$user->setNickname($this->request->post('nickname'));
$user->setPhone($this->request->post('phone'));
$user->setIdNumber($this->request->post('id_number'));
$user->setNickname(\Container::$request->post('nickname'));
$user->setPhone(\Container::$request->post('phone'));
$user->setIdNumber(\Container::$request->post('id_number'));
$this->pdm->saveToDb($user);
\Container::$persistentDataManager->saveToDb($user);
$this->request->session()->delete('authenticated_with_google_until');
\Container::$request->session()->delete('authenticated_with_google_until');
return new JsonContent(['success' => true]);
}
+38
View File
@@ -0,0 +1,38 @@
<?php namespace RVR\Controller;
use RVR\Repository\UserRepository;
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
use SokoWeb\Interfaces\Response\IContent;
use SokoWeb\Response\JsonContent;
class UserSearchController implements IAuthenticationRequired
{
private UserRepository $userRepository;
public function __construct()
{
$this->userRepository = new UserRepository();
}
public function isAuthenticationRequired(): bool
{
return true;
}
public function searchUser(): IContent
{
$users = iterator_to_array($this->userRepository->searchByName(\Container::$request->query('q')));
usort($users, function($a, $b) {
return strnatcmp($a->getDisplayName(), $b->getDisplayName());
});
$results = [];
foreach ($users as $user) {
$results[] = ['value' => $user->getId(), 'label' => $user->getFullDisplayName()];
}
return new JsonContent([
'results' => $results
]);
}
}
+15
View File
@@ -0,0 +1,15 @@
<?php namespace RVR\Database;
use SokoWeb\Database\AuditLoggerBase;
class AuditLogger extends AuditLoggerBase
{
protected function getModifierId()
{
$user = \Container::$request->user();
if ($user === null) {
return null;
}
return $user->getUniqueId();
}
}
+57
View File
@@ -0,0 +1,57 @@
<?php namespace RVR\PersistentData\Model;
use DateTime;
use SokoWeb\PersistentData\Model\Model;
class Community extends Model
{
protected static string $table = 'communities';
protected static array $fields = ['name', 'currency', 'created'];
private string $name = '';
private string $currency = '';
private DateTime $created;
public function setName(string $name): void
{
$this->name = $name;
}
public function setCurrency(string $currency): void
{
$this->currency = $currency;
}
public function setCreatedDate(DateTime $created): void
{
$this->created = $created;
}
public function setCreated(string $created): void
{
$this->created = new DateTime($created);
}
public function getName(): string
{
return $this->name;
}
public function getCurrency(): string
{
return $this->currency;
}
public function getCreatedDate(): DateTime
{
return $this->created;
}
public function getCreated(): string
{
return $this->created->format('Y-m-d H:i:s');
}
}
@@ -0,0 +1,72 @@
<?php namespace RVR\PersistentData\Model;
use SokoWeb\PersistentData\Model\Model;
class CommunityMember extends Model
{
protected static string $table = 'community_members';
protected static array $fields = ['community_id', 'user_id', 'owner'];
protected static array $relations = ['community' => Community::class, 'user' => User::class];
private ?Community $community = null;
private ?int $communityId = null;
private ?User $user = null;
private ?int $userId = null;
private bool $owner = false;
public function setCommunity(Community $community): void
{
$this->community = $community;
}
public function setCommunityId(int $communityId): void
{
$this->communityId = $communityId;
}
public function setUser(User $user): void
{
$this->user = $user;
}
public function setUserId(int $userId): void
{
$this->userId = $userId;
}
public function setOwner(bool $owner): void
{
$this->owner = $owner;
}
public function getCommunity(): ?Community
{
return $this->community;
}
public function getCommunityId(): ?int
{
return $this->communityId;
}
public function getUser(): ?User
{
return $this->user;
}
public function getUserId(): ?int
{
return $this->userId;
}
public function getOwner(): bool
{
return $this->owner;
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php namespace RVR\PersistentData\Model;
use SokoWeb\PersistentData\Model\Model;
class Currency extends Model
{
protected static string $table = 'currencies';
protected static array $fields = ['community_id', 'code', 'round_digits'];
protected static array $relations = ['community' => Community::class];
private ?Community $community = null;
private ?int $communityId = null;
private string $code = '';
private int $roundDigits = 0;
public function setCommunity(Community $community): void
{
$this->community = $community;
}
public function setCommunityId(int $communityId): void
{
$this->communityId = $communityId;
}
public function setCode(string $code): void
{
$this->code = $code;
}
public function setRoundDigits(int $roundDigits): void
{
$this->roundDigits = $roundDigits;
}
public function getCommunity(): ?Community
{
return $this->community;
}
public function getCommunityId(): ?int
{
return $this->communityId;
}
public function getCode(): string
{
return $this->code;
}
public function getRoundDigits(): int
{
return $this->roundDigits;
}
}
@@ -0,0 +1,71 @@
<?php namespace RVR\PersistentData\Model;
use DateTime;
use SokoWeb\PersistentData\Model\Model;
class CurrencyExchangeRate extends Model
{
protected static string $table = 'currency_exchange_rates';
protected static array $fields = ['currency_id', 'exchange_rate', 'valid_from'];
protected static array $relations = ['currency' => Currency::class];
private ?Currency $currency = null;
private ?int $currencyId = null;
private float $exchangeRate = 0.0;
private DateTime $validFrom;
public function setCurrency(Currency $currency): void
{
$this->currency = $currency;
}
public function setCurrencyId(int $currencyId): void
{
$this->currencyId = $currencyId;
}
public function setExchangeRate(float $exchangeRate): void
{
$this->exchangeRate = $exchangeRate;
}
public function setValidFromDate(DateTime $validFrom): void
{
$this->validFrom = $validFrom;
}
public function setValidFrom(string $validFrom): void
{
$this->validFrom = new DateTime($validFrom);
}
public function getCurrency(): ?Currency
{
return $this->currency;
}
public function getCurrencyId(): ?int
{
return $this->currencyId;
}
public function getExchangeRate(): float
{
return $this->exchangeRate;
}
public function getValidFromDate(): DateTime
{
return $this->validFrom;
}
public function getValidFrom(): string
{
return $this->validFrom->format('Y-m-d H:i:s');
}
}
+91
View File
@@ -0,0 +1,91 @@
<?php namespace RVR\PersistentData\Model;
use DateTime;
use SokoWeb\PersistentData\Model\Model;
class OAuthClient extends Model
{
protected static string $table = 'oauth_clients';
protected static array $fields = ['client_id', 'client_secret', 'redirect_uris', 'preapproved', 'created'];
private string $clientId = '';
private string $clientSecret = '';
private array $redirectUris = [];
private bool $preapproved = false;
private DateTime $created;
public function setClientId(string $clientId): void
{
$this->clientId = $clientId;
}
public function setClientSecret(string $clientSecret): void
{
$this->clientSecret = $clientSecret;
}
public function setRedirectUrisArray(array $redirectUris): void
{
$this->redirectUris = $redirectUris;
}
public function setRedirectUris(string $redirectUris): void
{
$this->redirectUris = json_decode($redirectUris, true);
}
public function setPreapproved(bool $preapproved): void
{
$this->preapproved = $preapproved;
}
public function setCreatedDate(DateTime $created): void
{
$this->created = $created;
}
public function setCreated(string $created): void
{
$this->created = new DateTime($created);
}
public function getClientId(): string
{
return $this->clientId;
}
public function getClientSecret(): string
{
return $this->clientSecret;
}
public function getRedirectUrisArray(): array
{
return $this->redirectUris;
}
public function getRedirectUris(): string
{
return json_encode($this->redirectUris);
}
public function getPreapproved(): bool
{
return $this->preapproved;
}
public function getCreatedDate(): DateTime
{
return $this->created;
}
public function getCreated(): string
{
return $this->created->format('Y-m-d H:i:s');
}
}
+37 -1
View File
@@ -7,10 +7,14 @@ class OAuthToken extends Model
{
protected static string $table = 'oauth_tokens';
protected static array $fields = ['nonce', 'user_id', 'code', 'created', 'expires'];
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;
@@ -19,10 +23,22 @@ class OAuthToken extends Model
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;
@@ -43,6 +59,11 @@ class OAuthToken extends Model
$this->code = $code;
}
public function setAccessToken(string $accessToken): void
{
$this->accessToken = $accessToken;
}
public function setCreatedDate(DateTime $created): void
{
$this->created = $created;
@@ -63,6 +84,16 @@ class OAuthToken extends Model
$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;
@@ -83,6 +114,11 @@ class OAuthToken extends Model
return $this->code;
}
public function getAccessToken(): string
{
return $this->accessToken;
}
public function getCreatedDate(): DateTime
{
return $this->created;
+5
View File
@@ -171,6 +171,11 @@ class User extends Model implements IUser
return $this->nickname ?: $this->fullName;
}
public function getFullDisplayName(): string
{
return $this->nickname ? $this->nickname . ' (' . $this->fullName . ')' : $this->fullName;
}
public function checkPassword(string $password): bool
{
if ($this->password === null) {
@@ -0,0 +1,40 @@
<?php namespace RVR\Repository;
use Generator;
use RVR\PersistentData\Model\Community;
use RVR\PersistentData\Model\CommunityMember;
use RVR\PersistentData\Model\User;
use SokoWeb\Database\Query\Select;
class CommunityMemberRepository
{
public function getById(int $id): ?CommunityMember
{
return \Container::$persistentDataManager->selectFromDbById($id, CommunityMember::class);
}
public function getAllByCommunity(Community $community, bool $useRelations = false): Generator
{
$select = new Select(\Container::$dbConnection);
$select->where('community_id', '=', $community->getId());
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, CommunityMember::class, $useRelations);
}
public function getAllByUser(User $user, bool $useRelations = false): Generator
{
$select = new Select(\Container::$dbConnection);
$select->where('user_id', '=', $user->getId());
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, CommunityMember::class, $useRelations);
}
public function getByCommunityAndUser(Community $community, User $user) : ?CommunityMember
{
$select = new Select(\Container::$dbConnection);
$select->where('community_id', '=', $community->getId());
$select->where('user_id', '=', $user->getId());
return \Container::$persistentDataManager->selectFromDb($select, CommunityMember::class);
}
}
+11
View File
@@ -0,0 +1,11 @@
<?php namespace RVR\Repository;
use RVR\PersistentData\Model\Community;
class CommunityRepository
{
public function getById(int $id): ?Community
{
return \Container::$persistentDataManager->selectFromDbById($id, Community::class);
}
}
@@ -0,0 +1,24 @@
<?php namespace RVR\Repository;
use Generator;
use Container;
use RVR\PersistentData\Model\Currency;
use RVR\PersistentData\Model\CurrencyExchangeRate;
use SokoWeb\Database\Query\Select;
class CurrencyExchangeRateRepository
{
public function getById(int $id): ?CurrencyExchangeRate
{
return \Container::$persistentDataManager->selectFromDbById($id, CurrencyExchangeRate::class);
}
public function getAllByCurrency(Currency $currency): Generator
{
$select = new Select(Container::$dbConnection);
$select->where('currency_id', '=', $currency->getId());
$select->orderBy('valid_from');
yield from Container::$persistentDataManager->selectMultipleFromDb($select, CurrencyExchangeRate::class);
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php namespace RVR\Repository;
use Generator;
use Container;
use RVR\PersistentData\Model\Community;
use RVR\PersistentData\Model\Currency;
use SokoWeb\Database\Query\Select;
class CurrencyRepository
{
public function getById(int $id): ?Currency
{
return \Container::$persistentDataManager->selectFromDbById($id, Currency::class);
}
public function getAllByCommunity(Community $community, bool $useRelations = false): Generator
{
$select = new Select(Container::$dbConnection);
$select->where('community_id', '=', $community->getId());
yield from Container::$persistentDataManager->selectMultipleFromDb($select, Currency::class, $useRelations);
}
public function getByCommunityAndCurrencyCode(Community $community, string $code): ?Currency
{
$select = new Select(Container::$dbConnection);
$select->where('community_id', '=', $community->getId());
$select->where('code', '=', $code);
return Container::$persistentDataManager->selectFromDb($select, Currency::class);
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php namespace RVR\Repository;
use SokoWeb\Database\Query\Select;
use RVR\PersistentData\Model\OAuthClient;
class OAuthClientRepository
{
public function getById(int $id): ?OAuthClient
{
return \Container::$persistentDataManager->selectFromDbById($id, OAuthClient::class);
}
public function getByClientId(string $clientId): ?OAuthClient
{
$select = new Select(\Container::$dbConnection);
$select->where('client_id', '=', $clientId);
return \Container::$persistentDataManager->selectFromDb($select, OAuthClient::class);
}
}
+11 -11
View File
@@ -4,20 +4,12 @@ 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);
return \Container::$persistentDataManager->selectFromDbById($id, OAuthToken::class);
}
public function getByCode(string $code): ?OAuthToken
@@ -25,7 +17,15 @@ class OAuthTokenRepository
$select = new Select(\Container::$dbConnection);
$select->where('code', '=', $code);
return $this->pdm->selectFromDb($select, OAuthToken::class);
return \Container::$persistentDataManager->selectFromDb($select, OAuthToken::class);
}
public function getByAccessToken(string $accessToken): ?OAuthToken
{
$select = new Select(\Container::$dbConnection);
$select->where('access_token', '=', $accessToken);
return \Container::$persistentDataManager->selectFromDb($select, OAuthToken::class);
}
public function getAllExpired(): Generator
@@ -33,6 +33,6 @@ class OAuthTokenRepository
$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);
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, OAuthToken::class);
}
}
@@ -5,20 +5,12 @@ use Generator;
use SokoWeb\Database\Query\Select;
use RVR\PersistentData\Model\User;
use RVR\PersistentData\Model\UserPasswordResetter;
use SokoWeb\PersistentData\PersistentDataManager;
class UserPasswordResetterRepository
{
private PersistentDataManager $pdm;
public function __construct()
{
$this->pdm = new PersistentDataManager();
}
public function getById(int $userConfirmationId): ?UserPasswordResetter
{
return $this->pdm->selectFromDbById($userConfirmationId, UserPasswordResetter::class);
return \Container::$persistentDataManager->selectFromDbById($userConfirmationId, UserPasswordResetter::class);
}
public function getByToken(string $token): ?UserPasswordResetter
@@ -26,7 +18,7 @@ class UserPasswordResetterRepository
$select = new Select(\Container::$dbConnection);
$select->where('token', '=', $token);
return $this->pdm->selectFromDb($select, UserPasswordResetter::class);
return \Container::$persistentDataManager->selectFromDb($select, UserPasswordResetter::class);
}
public function getByUser(User $user): ?UserPasswordResetter
@@ -34,7 +26,7 @@ class UserPasswordResetterRepository
$select = new Select(\Container::$dbConnection);
$select->where('user_id', '=', $user->getId());
return $this->pdm->selectFromDb($select, UserPasswordResetter::class);
return \Container::$persistentDataManager->selectFromDb($select, UserPasswordResetter::class);
}
public function getAllExpired(): Generator
@@ -42,6 +34,6 @@ class UserPasswordResetterRepository
$select = new Select(\Container::$dbConnection);
$select->where('expires', '<', (new DateTime())->format('Y-m-d H:i:s'));
yield from $this->pdm->selectMultipleFromDb($select, UserPasswordResetter::class);
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, UserPasswordResetter::class);
}
}
+15 -12
View File
@@ -1,22 +1,15 @@
<?php namespace RVR\Repository;
use Generator;
use SokoWeb\Interfaces\Repository\IUserRepository;
use SokoWeb\Database\Query\Select;
use RVR\PersistentData\Model\User;
use SokoWeb\PersistentData\PersistentDataManager;
class UserRepository implements IUserRepository
{
private PersistentDataManager $pdm;
public function __construct()
{
$this->pdm = new PersistentDataManager();
}
public function getById(int $userId): ?User
{
return $this->pdm->selectFromDbById($userId, User::class);
return \Container::$persistentDataManager->selectFromDbById($userId, User::class);
}
public function getByEmail(string $email): ?User
@@ -24,7 +17,7 @@ class UserRepository implements IUserRepository
$select = new Select(\Container::$dbConnection);
$select->where('email', '=', $email);
return $this->pdm->selectFromDb($select, User::class);
return \Container::$persistentDataManager->selectFromDb($select, User::class);
}
public function getByUsername(string $username): ?User
@@ -32,7 +25,7 @@ class UserRepository implements IUserRepository
$select = new Select(\Container::$dbConnection);
$select->where('username', '=', $username);
return $this->pdm->selectFromDb($select, User::class);
return \Container::$persistentDataManager->selectFromDb($select, User::class);
}
public function getByEmailOrUsername(string $emailOrUsername): ?User
@@ -49,6 +42,16 @@ class UserRepository implements IUserRepository
$select = new Select(\Container::$dbConnection);
$select->where('google_sub', '=', $sub);
return $this->pdm->selectFromDb($select, User::class);
return \Container::$persistentDataManager->selectFromDb($select, User::class);
}
public function searchByName(string $name): Generator
{
$select = new Select(\Container::$dbConnection);
$select->where('full_name', 'LIKE', '%' . $name . '%');
$select->orWhere('nickname', 'LIKE', '%' . $name . '%');
$select->limit(10);
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, User::class);
}
}
+7 -1
View File
@@ -5,7 +5,7 @@
@section(main)
<h2>Account</h2>
<div class="box">
<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">
<form id="accountForm" action="<?= Container::$routeCollection->getRoute('account-action')->generateLink() ?>" 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): ?>
<p class="justify small">Please confirm your identity with your password or with Google to modify your account.</p>
<div class="inputWithButton">
@@ -39,3 +39,9 @@
</form>
</div>
@endsection
@section(pageScript)
<script>
var googleAuthenticateUrl = '<?= Container::$routeCollection->getRoute('account.googleAuthenticate')->generateLink() ?>';
</script>
@endsection
+60
View File
@@ -0,0 +1,60 @@
@extends(templates/layout_normal)
@section(main)
<h2><?= $community->getName() ?> <span class="small">[<a href="<?= Container::$routeCollection->getRoute('community-edit')->generateLink(['communityId' => $community->getId()]) ?>">edit</a>]</span></h2>
<div class="gridContainer marginTop">
<div>
<h3 class="marginBottom">Members</h3>
<?php foreach ($members as $member): ?>
<p><?= $member->getUser()->getDisplayName() ?></p>
<?php endforeach; ?>
<?php if ($editPermission): ?>
<hr>
<p><a href="<?= Container::$routeCollection->getRoute('community-members')->generateLink(['communityId' => $community->getId()]) ?>">Edit members</a></p>
<?php endif; ?>
</div>
<div>
<h3 class="marginBottom">Currencies</h3>
<p>Main currency: <b><?= $community->getCurrency() ?></b></p>
<?php if (count($currencies) > 0): ?>
<p>Further currencies: <b>
<?php foreach ($currencies as $currency): ?>
<a href="<?= Container::$routeCollection->getRoute('community-currency-exchange-rates')->generateLink(['communityId' => $community->getId(), 'code' => $currency->getCode()]) ?>"><?= $currency->getCode() ?></a>
<?php endforeach; ?>
</b></p>
<?php endif; ?>
<?php if ($editPermission): ?>
<hr>
<p><a href="<?= Container::$routeCollection->getRoute('community-currencies')->generateLink(['communityId' => $community->getId()]) ?>">Edit currencies</a></p>
<?php endif; ?>
</div>
<div>
<h3 class="marginBottom">Upcoming events</h3>
<?php if (count($upcomingEvents) > 0): ?>
<?php foreach ($upcomingEvents as $event): ?>
<!-- todo -->
<?php endforeach; ?>
<?php else: ?>
<p>There is no upcoming event.</p>
<?php endif; ?>
</div>
<div>
<h3 class="marginBottom">Finances</h3>
<table class="fullWidth">
<tr>
<td>You owe</td>
<td style="text-align: right; color: red;">0 <?= $community->getCurrency() ?></td>
</tr>
<tr>
<td>You're owed</td>
<td style="text-align: right; color: green;">0 <?= $community->getCurrency() ?></td>
</tr>
<tr>
<td>Your balance</td>
<td style="text-align: right;">0 <?= $community->getCurrency() ?></td>
</tr>
</table>
</div>
</div>
@endsection
@@ -0,0 +1,45 @@
@extends(templates/layout_normal)
@section(main)
<h2><a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> - Edit currencies</h2>
<div class="box">
<table class="fullWidth">
<thead>
<tr>
<th style="width: calc(100% / 3); text-align: left;">Currency</th>
<th style="width: calc(100% / 3); text-align: left;">Round digits</th>
<th style="width: calc(100% / 3);"></th>
</tr>
</thead>
<?php foreach ($currencies as $currency): ?>
<tr>
<td>
<form id="editCurrency_<?= $currency->getId() ?>" action="<?= Container::$routeCollection->getRoute('community-currencies-edit')->generateLink(['communityId' => $community->getId(), 'currency_id' => $currency->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="code,round_digits"></form>
<form id="deleteCurrency_<?= $currency->getId() ?>" action="<?= Container::$routeCollection->getRoute('community-currencies-delete')->generateLink(['communityId' => $community->getId(), 'currency_id' => $currency->getId()]) ?>" method="post" data-reload-on-success="true"></form>
<input type="text" form="editCurrency_<?= $currency->getId() ?>" class="text fullWidth" name="code" value="<?= $currency->getCode() ?>" maxlength="3" required>
</td>
<td>
<input type="number" form="editCurrency_<?= $currency->getId() ?>" class="text fullWidth" name="round_digits" value="<?= $currency->getRoundDigits() ?>" min="0" max="9" required>
</td>
<td style="text-align: right;">
<button type="submit" form="editCurrency_<?= $currency->getId() ?>" name="submit" class="small marginRight" disabled>Save</button><!--
--><button type="submit" form="deleteCurrency_<?= $currency->getId() ?>" class="small red">Delete</button>
</td>
</tr>
<?php endforeach; ?>
<tr>
<td>
<form id="newCurrency" action="<?= Container::$routeCollection->getRoute('community-currencies-new')->generateLink(['communityId' => $community->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="code,round_digits"></form>
<input type="text" form="newCurrency" class="text fullWidth" name="code" placeholder="Currency" maxlength="3" required>
</td>
<td>
<input type="number" form="newCurrency" class="text fullWidth" name="round_digits" placeholder="Round digits" min="0" max="9" required>
</td>
<td style="text-align: right;">
<button type="submit" form="newCurrency" name="submit" class="small" disabled>Add</button>
</td>
</tr>
</table>
<p class="formError justify marginTop"></p>
</div>
@endsection
+26
View File
@@ -0,0 +1,26 @@
@extends(templates/layout_normal)
@section(main)
<h2>
<?php if (isset($community)): ?>
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> - Edit
<?php else: ?>
New community
<?php endif; ?>
</h2>
<div class="box">
<?php
$formAction = isset($community) ?
Container::$routeCollection->getRoute('community-edit-action')->generateLink(['communityId' => $community->getId()]) :
Container::$routeCollection->getRoute('community-new-action')->generateLink();
?>
<form id="communityForm" action="<?= $formAction ?>" method="post" data-redirect-on-success="true">
<input type="text" class="text big fullWidth" name="name" placeholder="Name" value="<?= isset($community) ? $community->getName() : '' ?>" required>
<input type="text" class="text big fullWidth marginTop" name="currency" value="<?= isset($community) ? $community->getCurrency() : '' ?>" placeholder="Default currency" maxlength="3" required>
<p id="communityFormError" class="formError justify marginTop"></p>
<div class="right marginTop">
<button type="submit" name="submit"><?= isset($community) ? 'Save' : 'Create' ?></button>
</div>
</form>
</div>
@endsection
+58
View File
@@ -0,0 +1,58 @@
@css(node_modules/choices.js/public/assets/styles/choices.min.css)
@js(node_modules/choices.js/public/assets/scripts/choices.js)
@js(js/communities/community_members.js)
@extends(templates/layout_normal)
@section(main)
<h2><a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> - Edit members</h2>
<div class="box">
<table class="fullWidth">
<thead>
<tr>
<th style="width: 50%;"></th>
<th style="width: 25%; text-align: center;">Owner</th>
<th style="width: 25%;"></th>
</tr>
</thead>
<?php foreach ($members as $member): ?>
<?php $editable = $member->getUserId() !== Container::$request->user()->getUniqueId(); ?>
<tr>
<td>
<form id="editMember_<?= $member->getId() ?>" action="<?= Container::$routeCollection->getRoute('community-members-edit')->generateLink(['communityId' => $community->getId(), 'community_member_id' => $member->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="owner"></form>
<form id="deleteMember_<?= $member->getId() ?>" action="<?= Container::$routeCollection->getRoute('community-members-delete')->generateLink(['communityId' => $community->getId(), 'community_member_id' => $member->getId()]) ?>" method="post" data-reload-on-success="true"></form>
<?= $member->getUser()->getDisplayName() ?>
</td>
<td style="text-align: center;">
<input type="checkbox" form="editMember_<?= $member->getId() ?>" name="owner" <?= $member->getOwner() ? 'checked' : '' ?> <?= !$editable ? 'disabled' : '' ?> />
</td>
<td style="text-align: right;">
<?php if ($editable): ?>
<button type="submit" form="editMember_<?= $member->getId() ?>" name="submit" class="small marginRight" disabled>Save</button><!--
--><button type="submit" form="deleteMember_<?= $member->getId() ?>" class="small red">Delete</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<tr>
<td>
<form id="newMember" action="<?= Container::$routeCollection->getRoute('community-members-new')->generateLink(['communityId' => $community->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="user_id"></form>
<select type="text" form="newMember" name="user_id">
</td>
<td style="text-align: center;">
<input type="checkbox" form="newMember" name="owner" />
</td>
<td style="text-align: right;">
<button type="submit" form="newMember" name="submit" class="small" disabled>Add</button>
</td>
</tr>
</table>
<p class="formError justify marginTop"></p>
</div>
@endsection
@section(pageScript)
<script>
var searchUserUrl = '<?= Container::$routeCollection->getRoute('searchUser')->generateLink(['q' => 'QUERY']) ?>';
</script>
@endsection
@@ -0,0 +1,57 @@
@extends(templates/layout_normal)
@section(main)
<h2><a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> - Exchange rates for <?= $currency->getCode() ?></h2>
<div class="box">
<table class="fullWidth">
<thead>
<tr>
<?php if ($editPermission): ?>
<th style="width: 25%; text-align: left;">Exchange rate</th>
<th style="width: 50%; text-align: left;">Valid from</th>
<th style="width: 25%;"></th>
<?php else: ?>
<th style="width: 35%; text-align: left;">Exchange rate</th>
<th style="width: 65%; text-align: left;">Valid from</th>
<?php endif; ?>
</tr>
</thead>
<?php foreach ($currencyExchangeRates as $currencyExchangeRate): ?>
<tr>
<?php if ($editPermission): ?>
<td>
<form id="editExchangeRate_<?= $currencyExchangeRate->getId() ?>" action="<?= Container::$routeCollection->getRoute('community-currency-exchange-rates-edit')->generateLink(['communityId' => $community->getId(), 'code' => $currency->getCode(), 'currency_exchange_rate_id' => $currencyExchangeRate->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="exchange_rate,valid_from"></form>
<form id="deleteCurrency_<?= $currencyExchangeRate->getId() ?>" action="<?= Container::$routeCollection->getRoute('community-currency-exchange-rates-delete')->generateLink(['communityId' => $community->getId(), 'code' => $currency->getCode(), 'currency_exchange_rate_id' => $currencyExchangeRate->getId()]) ?>" method="post" data-reload-on-success="true"></form>
<input type="number" form="editExchangeRate_<?= $currencyExchangeRate->getId() ?>" class="text fullWidth" name="exchange_rate" value="<?= $currencyExchangeRate->getExchangeRate() ?>" min="0" step="0.000000001" required>
</td>
<td>
<input type="datetime-local" form="editExchangeRate_<?= $currencyExchangeRate->getId() ?>" class="text fullWidth" name="valid_from" value="<?= $currencyExchangeRate->getValidFromDate()->format('Y-m-d\TH:i') ?>" required>
</td>
<td style="text-align: right;">
<button type="submit" form="editExchangeRate_<?= $currencyExchangeRate->getId() ?>" name="submit" class="small marginRight" disabled>Save</button><!--
--><button type="submit" form="deleteCurrency_<?= $currencyExchangeRate->getId() ?>" class="small red">Delete</button>
</td>
<?php else: ?>
<td><?= $currencyExchangeRate->getExchangeRate() ?></td>
<td><?= $currencyExchangeRate->getValidFromDate()->format('Y-m-d H:i') ?></td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
<?php if ($editPermission): ?>
<tr>
<td>
<form id="newExchangeRate" action="<?= Container::$routeCollection->getRoute('community-currency-exchange-rates-new')->generateLink(['communityId' => $community->getId(), 'code' => $currency->getCode()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="exchange_rate,valid_from"></form>
<input type="number" form="newExchangeRate" class="text fullWidth" name="exchange_rate" placeholder="Exchange rate" min="0" step="0.000000001" required>
</td>
<td>
<input type="datetime-local" form="newExchangeRate" class="text fullWidth" name="valid_from" placeholder="Valid from" required>
</td>
<td style="text-align: right;">
<button type="submit" form="newExchangeRate" name="submit" class="small" disabled>Add</button>
</td>
</tr>
<?php endif; ?>
</table>
<p class="formError justify marginTop"></p>
</div>
@endsection
+1 -1
View File
@@ -2,5 +2,5 @@
@section(main)
<h2>404 | Page not found</h2>
<p>The requested URL was not found on this server. <a href="/" title="<?= $_ENV['APP_NAME'] ?>">Back to start.</a></p>
<p>The requested URL was not found on this server. <a href="<?= Container::$routeCollection->getRoute('home')->generateLink() ?>" title="<?= $_ENV['APP_NAME'] ?>">Back to start.</a></p>
@endsection
+11
View File
@@ -0,0 +1,11 @@
@extends(templates/layout_normal)
@section(main)
<h2>500 | Internal server error</h2>
<p>An error occured during processing your request. <a href="<?= Container::$routeCollection->getRoute('home')->generateLink() ?>" title="<?= $_ENV['APP_NAME'] ?>">Back to start.</a></p>
<?php if (isset($exceptionToPrint)): ?>
<pre class="marginTop">
<?= $exceptionToPrint ?>
</pre>
<?php endif; ?>
@endsection
+28
View File
@@ -0,0 +1,28 @@
@extends(templates/layout_normal)
@section(main)
<div class="gridContainer">
<div>
<h3 class="marginBottom">Communities</h3>
<?php if (count($communities) > 0): ?>
<?php foreach ($communities as $community): ?>
<p><a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a></p>
<?php endforeach; ?>
<?php else: ?>
<p>You have no community.</p>
<?php endif; ?>
<hr>
<p><a href="<?= Container::$routeCollection->getRoute('community-new')->generateLink() ?>">New community</a></p>
</div>
<div>
<h3 class="marginBottom">Upcoming events</h3>
<?php if (count($upcomingEvents) > 0): ?>
<?php foreach ($upcomingEvents as $event): ?>
<!-- todo -->
<?php endforeach; ?>
<?php else: ?>
<p>There is no upcoming event.</p>
<?php endif; ?>
</div>
</div>
@endsection
-8
View File
@@ -1,8 +0,0 @@
@extends(templates/layout_normal)
@section(main)
<h2><?= $_ENV['APP_NAME'] ?></h2>
<div class="box">
<p>This is the new <?= $_ENV['APP_NAME'] ?>.</p>
</div>
@endsection
+3 -3
View File
@@ -3,17 +3,17 @@
@section(main)
<h2>Login</h2>
<div class="box">
<form id="loginForm" action="/login" method="post" data-redirect-on-success="<?= $redirectUrl ?>">
<form id="loginForm" action="<?= Container::$routeCollection->getRoute('login-action')->generateLink() ?>" method="post" data-redirect-on-success="<?= $redirectUrl ?>">
<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">
<p id="loginFormError" class="formError justify marginTop"></p>
<div class="right marginTop">
<button type="submit">Login</button>
</div>
<p class="center marginTop"><a href="/password/requestReset" title="Request password reset">Forgot your password?</a></p>
<p class="center marginTop"><a href="<?= Container::$routeCollection->getRoute('password-requestReset')->generateLink() ?>" title="Request password reset">Forgot your password?</a></p>
<hr>
<div class="center">
<a class="button yellow" href="/login/google" title="Login with Google">Login with Google</a>
<a class="button yellow" href="<?= Container::$routeCollection->getRoute('login-google')->generateLink() ?>" title="Login with Google">Login with Google</a>
</div>
</form>
</div>
+1 -1
View File
@@ -5,7 +5,7 @@
@section(main)
<h2>Request password reset</h2>
<div class="box">
<form id="passwordResetForm" action="/password/requestReset" method="post" data-redirect-on-success="/password/requestReset/success">
<form id="passwordResetForm" action="<?= Container::$routeCollection->getRoute('password-requestReset-action')->generateLink() ?>" method="post" data-redirect-on-success="<?= Container::$routeCollection->getRoute('password-requestReset.success')->generateLink() ?>">
<input type="email" class="text big fullWidth" name="email" placeholder="Email address" value="<?= isset($email) ? $email : '' ?>" required autofocus>
<?php if (!empty($_ENV['RECAPTCHA_SITEKEY'])): ?>
<div class="marginTop">
+2 -2
View File
@@ -4,7 +4,7 @@
<h2>Reset password</h2>
<div class="box">
<?php if ($success) : ?>
<form id="resetPasswordForm" action="/password/reset/<?= $token ?>" method="post" data-redirect-on-success="<?= $redirectUrl ?>">
<form id="resetPasswordForm" action="<?= Container::$routeCollection->getRoute('password-reset.action')->generateLink(['token' => $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="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">
@@ -14,7 +14,7 @@
</div>
</form>
<?php else: ?>
<p class="error justify">Confirming your identity failed. Please check the link you entered, or retry <a href="/password/requestReset" title="Request password reset">requesting password reset</a>!</p>
<p class="error justify">Confirming your identity failed. Please check the link you entered, or retry <a href="<?= Container::$routeCollection->getRoute('password-requestReset')->generateLink() ?>" title="Request password reset">requesting password reset</a>!</p>
<?php endif; ?>
</div>
@endsection
+5 -5
View File
@@ -3,21 +3,21 @@
@section(content)
<header>
<h1>
<a href="/" title="<?= $_ENV['APP_NAME'] ?>">
<a href="<?= Container::$routeCollection->getRoute('home')->generateLink() ?>" title="<?= $_ENV['APP_NAME'] ?>">
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>" alt="<?= $_ENV['APP_NAME'] ?>"><!--
--><span><?= $_ENV['APP_NAME'] ?></span>
</a>
</h1>
<p>
<?php if (Container::$request->user()) : ?>
<span><a href="/account" title="Account">
<span><a href="<?= Container::$routeCollection->getRoute('account')->generateLink() ?>" title="Account">
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
<svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
</svg><!--
--><?= Container::$request->user()->getDisplayName() ?><!--
--></a></span><!--
--><span><a href="/logout" title="Logout">Logout</a></span>
--><span><a href="<?= Container::$routeCollection->getRoute('logout')->generateLink() ?>" title="Logout">Logout</a></span>
<?php endif; ?>
</p>
</header>
@@ -25,8 +25,8 @@
@yields('main')
</main>
<footer>
<p><?= round($__debug_runtime, 0) ?> ms</p>
<p><span class="bold"><?= $_ENV['APP_NAME'] ?></span> <?= str_replace('Release_', '', VERSION) ?></p><!--
<p><?= round($__debug_runtime, 0) ?> ms</p><!--
--><p><span class="bold"><?= $_ENV['APP_NAME'] ?></span> <?= str_replace('Release_', '', VERSION) ?></p><!--
--><p>&copy; The RVR Contributors <?= (new DateTime(REVISION_DATE))->format('Y') ?></p>
</footer>
@endsection
+85 -35
View File
@@ -1,5 +1,18 @@
<?php
use SokoWeb\Response\HttpResponse;
use SokoWeb\Routing\RouteCollection;
use SokoWeb\Session\DatabaseSessionHandler;
use SokoWeb\Request\Request;
use RVR\Controller\HomeController;
use RVR\Controller\LoginController;
use RVR\Controller\OAuthAuthController;
use RVR\Controller\OAuthController;
use RVR\Controller\UserController;
use RVR\Controller\UserSearchController;
use RVR\Controller\CommunityController;
use RVR\Repository\UserRepository;
require 'app.php';
if (!empty($_ENV['DEV'])) {
@@ -10,38 +23,63 @@ if (!empty($_ENV['DEV'])) {
ini_set('display_errors', '0');
}
Container::$routeCollection = new SokoWeb\Routing\RouteCollection();
Container::$routeCollection = new RouteCollection();
Container::$routeCollection->get('index', '', [RVR\Controller\HomeController::class, 'getIndex']);
Container::$routeCollection->get('startSession', 'startSession.json', [RVR\Controller\HomeController::class, 'startSession']);
Container::$routeCollection->group('login', function (SokoWeb\Routing\RouteCollection $routeCollection) {
$routeCollection->get('login', '', [RVR\Controller\LoginController::class, 'getLoginForm']);
$routeCollection->post('login-action', '', [RVR\Controller\LoginController::class, 'login']);
$routeCollection->get('login-google', 'google', [RVR\Controller\LoginController::class, 'getGoogleLoginRedirect']);
$routeCollection->get('login-google-action', 'google/code', [RVR\Controller\LoginController::class, 'loginWithGoogle']);
Container::$routeCollection->get('home', '', [HomeController::class, 'getHome']);
Container::$routeCollection->get('startSession', 'startSession.json', [HomeController::class, 'startSession']);
Container::$routeCollection->group('login', function (RouteCollection $routeCollection) {
$routeCollection->get('login', '', [LoginController::class, 'getLoginForm']);
$routeCollection->post('login-action', '', [LoginController::class, 'login']);
$routeCollection->get('login-google', 'google', [LoginController::class, 'getGoogleLoginRedirect']);
$routeCollection->get('login-google-action', 'google/code', [LoginController::class, 'loginWithGoogle']);
});
Container::$routeCollection->group('oauth', function (SokoWeb\Routing\RouteCollection $routeCollection) {
$routeCollection->get('oauth-start', 'start', [RVR\Controller\OAuthLoginController::class, 'startOauth']);
$routeCollection->get('oauth-finish', 'finish', [RVR\Controller\OAuthLoginController::class, 'finishOauth']);
$routeCollection->post('oauth-token', 'token', [RVR\Controller\OAuthLoginController::class, 'getToken']);
$routeCollection->get('oauth-jwtPublicKey', 'jwtPublicKey', [RVR\Controller\OAuthLoginController::class, 'getJwtPublicKey']);
Container::$routeCollection->group('oauth', function (RouteCollection $routeCollection) {
$routeCollection->get('oauth-auth', 'auth', [OAuthAuthController::class, 'auth']);
$routeCollection->post('oauth-token', 'token', [OAuthController::class, 'getToken']);
$routeCollection->get('oauth-userinfo', 'userinfo', [OAuthController::class, 'getUserInfo']);
$routeCollection->get('oauth-config', '.well-known/openid-configuration', [OAuthController::class, 'getConfig']);
$routeCollection->get('oauth-certs', 'certs', [OAuthController::class, 'getCerts']);
});
Container::$routeCollection->group('password', function (SokoWeb\Routing\RouteCollection $routeCollection) {
$routeCollection->get('password-requestReset', 'requestReset', [RVR\Controller\LoginController::class, 'getRequestPasswordResetForm']);
$routeCollection->post('password-requestReset-action', 'requestReset', [RVR\Controller\LoginController::class, 'requestPasswordReset']);
$routeCollection->get('password-requestReset.success', 'requestReset/success', [RVR\Controller\LoginController::class, 'getRequestPasswordResetSuccess']);
$routeCollection->get('password-reset', 'reset/{token}', [RVR\Controller\LoginController::class, 'getResetPasswordForm']);
$routeCollection->post('password-reset.action', 'reset/{token}', [RVR\Controller\LoginController::class, 'resetPassword']);
Container::$routeCollection->group('password', function (RouteCollection $routeCollection) {
$routeCollection->get('password-requestReset', 'requestReset', [LoginController::class, 'getRequestPasswordResetForm']);
$routeCollection->post('password-requestReset-action', 'requestReset', [LoginController::class, 'requestPasswordReset']);
$routeCollection->get('password-requestReset.success', 'requestReset/success', [LoginController::class, 'getRequestPasswordResetSuccess']);
$routeCollection->get('password-reset', 'reset/{token}', [LoginController::class, 'getResetPasswordForm']);
$routeCollection->post('password-reset.action', 'reset/{token}', [LoginController::class, 'resetPassword']);
});
Container::$routeCollection->get('logout', 'logout', [RVR\Controller\LoginController::class, 'logout']);
Container::$routeCollection->group('account', function (SokoWeb\Routing\RouteCollection $routeCollection) {
$routeCollection->get('account', '', [RVR\Controller\UserController::class, 'getAccount']);
$routeCollection->post('account-action', '', [RVR\Controller\UserController::class, 'saveAccount']);
$routeCollection->get('account.googleAuthenticate', 'googleAuthenticate', [RVR\Controller\UserController::class, 'getGoogleAuthenticateRedirect']);
$routeCollection->get('account.googleAuthenticate-action', 'googleAuthenticate/code', [RVR\Controller\UserController::class, 'authenticateWithGoogle']);
Container::$routeCollection->get('logout', 'logout', [LoginController::class, 'logout']);
Container::$routeCollection->group('account', function (RouteCollection $routeCollection) {
$routeCollection->get('account', '', [UserController::class, 'getAccount']);
$routeCollection->post('account-action', '', [UserController::class, 'saveAccount']);
$routeCollection->get('account.googleAuthenticate', 'googleAuthenticate', [UserController::class, 'getGoogleAuthenticateRedirect']);
$routeCollection->get('account.googleAuthenticate-action', 'googleAuthenticate/code', [UserController::class, 'authenticateWithGoogle']);
});
Container::$routeCollection->get('searchUser', 'searchUser', [UserSearchController::class, 'searchUser']);
Container::$routeCollection->group('communities', function (RouteCollection $routeCollection) {
$routeCollection->get('community-new', 'new', [CommunityController::class, 'getCommunityNew']);
$routeCollection->post('community-new-action', 'new', [CommunityController::class, 'saveCommunity']);
$routeCollection->group('{communityId}', function (RouteCollection $routeCollection) {
$routeCollection->get('community', '', [CommunityController::class, 'getCommunityHome']);
$routeCollection->get('community-edit', 'edit', [CommunityController::class, 'getCommunityEdit']);
$routeCollection->post('community-edit-action', 'edit', [CommunityController::class, 'saveCommunity']);
$routeCollection->get('community-members', 'members', [CommunityController::class, 'getMembersEdit']);
$routeCollection->post('community-members-new', 'newMember', [CommunityController::class, 'saveMember']);
$routeCollection->post('community-members-edit', 'editMember', [CommunityController::class, 'saveMember']);
$routeCollection->post('community-members-delete', 'deleteMember', [CommunityController::class, 'deleteMember']);
$routeCollection->get('community-currencies', 'currencies', [CommunityController::class, 'getCurrenciesEdit']);
$routeCollection->post('community-currencies-new', 'newCurrency', [CommunityController::class, 'saveCurrency']);
$routeCollection->post('community-currencies-edit', 'editCurrency', [CommunityController::class, 'saveCurrency']);
$routeCollection->post('community-currencies-delete', 'deleteCurrency', [CommunityController::class, 'deleteCurrency']);
$routeCollection->group('currencyExchangeRates', function (RouteCollection $routeCollection) {
$routeCollection->get('community-currency-exchange-rates', '{code}', [CommunityController::class, 'getCurrencyExchangeRates']);
$routeCollection->post('community-currency-exchange-rates-new', '{code}/new', [CommunityController::class, 'saveCurrencyExchangeRate']);
$routeCollection->post('community-currency-exchange-rates-edit', '{code}/edit', [CommunityController::class, 'saveCurrencyExchangeRate']);
$routeCollection->post('community-currency-exchange-rates-delete', '{code}/delete', [CommunityController::class, 'deleteCurrencyExchangeRate']);
});
});
});
Container::$sessionHandler = new SokoWeb\Session\DatabaseSessionHandler();
Container::$sessionHandler = new DatabaseSessionHandler(Container::$dbConnection);
session_set_save_handler(Container::$sessionHandler, true);
session_start([
@@ -62,22 +100,34 @@ if (isset($_COOKIE[session_name()])) {
]);
}
// this is needed to handle old type of session IDs
if (!Container::$sessionHandler->validateId(session_id())) {
session_regenerate_id(true);
}
Container::$request = new SokoWeb\Request\Request(
Container::$request = new Request(
$_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'],
$_GET,
$_POST,
getallheaders(),
$_SESSION,
new RVR\Repository\UserRepository()
new UserRepository()
);
if (!Container::$request->session()->has('anti_csrf_token')) {
Container::$request->session()->set('anti_csrf_token', bin2hex(random_bytes(16)));
}
//TODO: make a nicer logic
$antiCsrfTokenExceptions = ['oauth/token'];
$appConfig = [
'antiCsrfTokenName' => 'anti_csrf_token',
'antiCsrfTokenErrorResponse' => ['error' => 'no_valid_anti_csrf_token'],
'antiCsrfTokenExceptions' => ['/oauth/token'],
'loginRouteId' => 'login',
'error404View' => 'error/404',
'error500View' => 'error/500'
];
$httpReponse = new HttpResponse(
Container::$request,
Container::$dbConnection,
Container::$routeCollection,
$appConfig,
$_SERVER['REQUEST_METHOD'],
$_SERVER['REQUEST_URI']
);
$httpReponse->render();