parent
5adb29d700
commit
748bd98abf
15 changed files with 122 additions and 53 deletions
|
@ -1,12 +1,13 @@
|
|||
## v.3.0 (WIP)
|
||||
+ Upgraded from Slim3 to Slim 4.
|
||||
+ Added web upload.
|
||||
+ Added ability to add custom HTML in \<head\> tag.
|
||||
+ Added ability to show a preview of PDF files.
|
||||
+ Raw URL now accept file extensions.
|
||||
+ Improved installer.
|
||||
+ Improved thumbnail generation.
|
||||
+ Replaced videojs player with Plyr.
|
||||
+ Implemented SameSite XSS protection.
|
||||
+ Added ability to add custom HTML in <head> tag.
|
||||
+ Small fixes and improvements.
|
||||
|
||||
## v.2.6.6
|
||||
|
|
|
@ -28,6 +28,7 @@ class LoginController extends Controller
|
|||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return Response
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function login(Request $request, Response $response): Response
|
||||
{
|
||||
|
@ -58,6 +59,30 @@ class LoginController extends Controller
|
|||
$this->session->alert(lang('welcome', [$result->username]), 'info');
|
||||
$this->logger->info("User $result->username logged in.");
|
||||
|
||||
if (param($request, 'remember') === 'on') {
|
||||
$selector = bin2hex(random_bytes(8));
|
||||
$token = bin2hex(random_bytes(32));
|
||||
$expire = time() + 604800; // a week
|
||||
|
||||
$this->database->query('UPDATE `users` SET `remember_selector`=?, `remember_token`=?, `remember_expire`=? WHERE `id`=?', [
|
||||
$selector,
|
||||
password_hash($token, PASSWORD_DEFAULT),
|
||||
date('Y-m-d\TH:i:s', $expire),
|
||||
$result->id,
|
||||
]);
|
||||
|
||||
// Workaround for php <= 7.3
|
||||
if (PHP_VERSION_ID < 70300) {
|
||||
setcookie('remember', "{$selector}:{$token}", $expire, '; SameSite=Lax', '', false, true);
|
||||
} else {
|
||||
setcookie('remember', "{$selector}:{$token}", [
|
||||
'expires' => $expire,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->session->has('redirectTo')) {
|
||||
return redirect($response, $this->session->get('redirectTo'));
|
||||
}
|
||||
|
@ -66,14 +91,20 @@ class LoginController extends Controller
|
|||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return Response
|
||||
*/
|
||||
public function logout(Response $response): Response
|
||||
public function logout(Request $request,Response $response): Response
|
||||
{
|
||||
$this->session->clear();
|
||||
$this->session->set('logged', false);
|
||||
$this->session->alert(lang('goodbye'), 'warning');
|
||||
|
||||
if (!empty($request->getCookieParams()['remember'])) {
|
||||
setcookie('remember', null);
|
||||
}
|
||||
|
||||
return redirect($response, route('login.show'));
|
||||
}
|
||||
|
||||
|
|
|
@ -250,6 +250,7 @@ class UploadController extends Controller
|
|||
* @param string|null $ext
|
||||
* @return Response
|
||||
* @throws FileNotFoundException
|
||||
* @throws HttpBadRequestException
|
||||
* @throws HttpNotFoundException
|
||||
*/
|
||||
public function showRaw(Request $request, Response $response, string $userCode, string $mediaCode, ?string $ext = null): Response
|
||||
|
|
|
@ -2,50 +2,13 @@
|
|||
|
||||
namespace App\Middleware;
|
||||
|
||||
|
||||
use App\Database\DB;
|
||||
use App\Web\Lang;
|
||||
use App\Web\Session;
|
||||
use App\Web\View;
|
||||
use DI\Container;
|
||||
use League\Flysystem\Filesystem;
|
||||
use Monolog\Logger;
|
||||
use App\Controllers\Controller;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
|
||||
|
||||
/**
|
||||
* @property Session|null session
|
||||
* @property View view
|
||||
* @property DB|null database
|
||||
* @property Logger|null logger
|
||||
* @property Filesystem|null storage
|
||||
* @property Lang lang
|
||||
* @property array config
|
||||
*/
|
||||
abstract class Middleware
|
||||
abstract class Middleware extends Controller
|
||||
{
|
||||
/** @var Container */
|
||||
protected $container;
|
||||
|
||||
public function __construct(Container $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @return mixed|null
|
||||
* @throws \DI\DependencyException
|
||||
* @throws \DI\NotFoundException
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
if ($this->container->has($name)) {
|
||||
return $this->container->get($name);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
|
|
40
app/Middleware/RememberMiddleware.php
Normal file
40
app/Middleware/RememberMiddleware.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace App\Middleware;
|
||||
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
|
||||
|
||||
class RememberMiddleware extends Middleware
|
||||
{
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param RequestHandler $handler
|
||||
* @return Response
|
||||
*/
|
||||
public function __invoke(Request $request, RequestHandler $handler)
|
||||
{
|
||||
if (!$this->session->get('logged', false) && !empty($request->getCookieParams()['remember'])) {
|
||||
list($selector, $token) = explode(':', $request->getCookieParams()['remember']);
|
||||
|
||||
$result = $this->database->query('SELECT `id`, `email`, `username`,`is_admin`, `active`, `remember_token` FROM `users` WHERE `remember_selector` = ? AND `remember_expire` > ? LIMIT 1',
|
||||
[$selector, date('Y-m-d\TH:i:s', time())]
|
||||
)->fetch();
|
||||
|
||||
if ($result && password_verify($token, $result->remember_token) && $result->active) {
|
||||
$this->session->set('logged', true);
|
||||
$this->session->set('user_id', $result->id);
|
||||
$this->session->set('username', $result->username);
|
||||
$this->session->set('admin', $result->is_admin);
|
||||
$this->session->set('used_space', humanFileSize($this->getUsedSpaceByUser($result->id)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
|
@ -28,7 +28,7 @@ if (!function_exists('humanRandomString')) {
|
|||
* @param int $length
|
||||
* @return string
|
||||
*/
|
||||
function humanRandomString(int $length = 13): string
|
||||
function humanRandomString(int $length = 10): string
|
||||
{
|
||||
$result = '';
|
||||
$numberOffset = round($length * 0.2);
|
||||
|
|
|
@ -5,6 +5,7 @@ use App\Exception\Handlers\AppErrorHandler;
|
|||
use App\Exception\Handlers\Renderers\HtmlErrorRenderer;
|
||||
use App\Factories\ViewFactory;
|
||||
use App\Middleware\InjectMiddleware;
|
||||
use App\Middleware\RememberMiddleware;
|
||||
use App\Web\Lang;
|
||||
use App\Web\Session;
|
||||
use Aws\S3\S3Client;
|
||||
|
@ -149,7 +150,8 @@ if (!$config['debug']) {
|
|||
$app->getRouteCollector()->setCacheFile(BASE_DIR.'resources/cache/routes.cache.php');
|
||||
}
|
||||
|
||||
$app->addRoutingMiddleware();
|
||||
$app->add(InjectMiddleware::class);
|
||||
$app->add(RememberMiddleware::class);
|
||||
|
||||
// Permanently redirect paths with a trailing slash to their non-trailing counterpart
|
||||
$app->add(function (Request $request, RequestHandler $handler) {
|
||||
|
@ -173,7 +175,7 @@ $app->add(function (Request $request, RequestHandler $handler) {
|
|||
return $handler->handle($request);
|
||||
});
|
||||
|
||||
$app->add(InjectMiddleware::class);
|
||||
$app->addRoutingMiddleware();
|
||||
|
||||
// Configure the error handler
|
||||
$errorHandler = new AppErrorHandler($app->getCallableResolver(), $app->getResponseFactory());
|
||||
|
|
|
@ -107,4 +107,5 @@ return [
|
|||
'custom_head_html' => 'Custom HTML Head content',
|
||||
'custom_head_html_hint' => 'This content will be added at the <head> tag on every page.',
|
||||
'custom_head_set' => 'Custom Head HTML applied successfully.',
|
||||
'remember_me' => 'Remember me',
|
||||
];
|
||||
|
|
7
resources/schemas/mysql/mysql.4.sql
Normal file
7
resources/schemas/mysql/mysql.4.sql
Normal file
|
@ -0,0 +1,7 @@
|
|||
ALTER TABLE `users`
|
||||
ADD COLUMN `remember_selector` VARCHAR(16),
|
||||
ADD COLUMN `remember_token` VARCHAR(256),
|
||||
ADD COLUMN `remember_expire` TIMESTAMP;
|
||||
|
||||
ALTER TABLE `users` ADD INDEX (`remember_selector`);
|
||||
|
6
resources/schemas/sqlite/sqlite.4.sql
Normal file
6
resources/schemas/sqlite/sqlite.4.sql
Normal file
|
@ -0,0 +1,6 @@
|
|||
ALTER TABLE `users` ADD COLUMN `remember_selector` VARCHAR(16);
|
||||
ALTER TABLE `users` ADD COLUMN `remember_token` VARCHAR(256);
|
||||
ALTER TABLE `users` ADD COLUMN `remember_expire` TIMESTAMP;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS `remember_selector_index`
|
||||
ON `users` (`remember_selector`);
|
|
@ -26,8 +26,8 @@
|
|||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<form class="form-signin text-center" method="post" action="{{ route('login') }}">
|
||||
<div class="row">
|
||||
<form class="form-signin" method="post" action="{{ route('login') }}">
|
||||
<div class="row text-center">
|
||||
<div class="col-md-12">
|
||||
<h1 class="h3 mb-3 font-weight-normal">{{ config.app_name }}</h1>
|
||||
{% include 'comp/alert.twig' %}
|
||||
|
@ -39,9 +39,13 @@
|
|||
<input type="text" id="username" class="form-control" placeholder="{{ lang('login.username') }}" name="username" required autofocus>
|
||||
<label for="password" class="sr-only">{{ lang('password') }}</label>
|
||||
<input type="password" id="password" class="form-control" placeholder="{{ lang('password') }}" name="password" required>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" name="remember" class="form-check-input float-left" id="remember">
|
||||
<label class="form-check-label" for="remember">{{ lang('remember_me') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="row mt-2">
|
||||
<div class="col-md-12">
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">{{ lang('login') }}</button>
|
||||
</div>
|
||||
|
|
|
@ -24,8 +24,8 @@
|
|||
<a class="dropdown-item" href="{{ queryParams({'sort':'size'}) }}"><i class="fas fa-weight-hanging fa-fw"></i> {{ lang('size') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="{{ queryParams({'order': request.param('order') is same as('ASC') ? 'DESC' : 'ASC' }) }}" class="btn btn-outline-info">
|
||||
<i class="fas {{ request.param('order') is same as('ASC') ? 'fa-sort-amount-up' : 'fa-sort-amount-down' }}"></i>
|
||||
<a href="{{ queryParams({'order': request.queryParams['order'] is same as('ASC') ? 'DESC' : 'ASC' }) }}" class="btn btn-outline-info">
|
||||
<i class="fas {{ request.queryParams['order'] is same as('ASC') ? 'fa-sort-amount-up' : 'fa-sort-amount-down' }}"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -67,6 +67,7 @@
|
|||
<audio id="player" autoplay controls loop preload="auto">
|
||||
<source src="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/raw') }}" type="{{ media.mimetype }}">
|
||||
Your browser does not support HTML5 audio.
|
||||
<a href="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/download') }}" class="btn btn-dark btn-lg"><i class="fas fa-cloud-download-alt fa-fw"></i> Download</a>
|
||||
</audio>
|
||||
</div>
|
||||
{% elseif type is same as ('video') %}
|
||||
|
@ -74,8 +75,14 @@
|
|||
<video id="player" autoplay controls loop preload="auto">
|
||||
<source src="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/raw') }}" type="{{ media.mimetype }}">
|
||||
Your browser does not support HTML5 video.
|
||||
<a href="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/download') }}" class="btn btn-dark btn-lg"><i class="fas fa-cloud-download-alt fa-fw"></i> Download</a>
|
||||
</video>
|
||||
</div>
|
||||
{% elseif media.mimetype is same as ('application/pdf') %}
|
||||
<object type="{{ media.mimetype }}" data="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/raw') }}" class="pdf-viewer">
|
||||
Your browser does not support PDF previews.
|
||||
<a href="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/download') }}" class="btn btn-dark btn-lg"><i class="fas fa-cloud-download-alt fa-fw"></i> Download</a>
|
||||
</object>
|
||||
{% else %}
|
||||
<div class="text-center">
|
||||
<div class="row mb-3">
|
||||
|
@ -116,7 +123,8 @@
|
|||
<textarea type="text" class="form-control mb-2" id="telegram-share-text" onclick="this.select()">{{ media.filename }}</textarea>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-info btn-block" id="telegram-share-button" onclick="app.telegramShare()" data-url="https://telegram.me/share/url?url={{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension) }}&text="><i class="fab fa-telegram-plane fa-lg fa-fw"></i> {{ lang("send") }}</button>
|
||||
<button type="button" class="btn btn-info btn-block" id="telegram-share-button" onclick="app.telegramShare()" data-url="https://telegram.me/share/url?url={{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension) }}&text=">
|
||||
<i class="fab fa-telegram-plane fa-lg fa-fw"></i> {{ lang("send") }}</button>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ lang('cancel') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -35,7 +35,7 @@ body {
|
|||
}
|
||||
|
||||
.form-signin input[type="password"] {
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: .50rem;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
@ -95,7 +95,6 @@ body {
|
|||
width: 77%;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
|
||||
}
|
||||
|
||||
.media-audio {
|
||||
|
@ -106,6 +105,7 @@ body {
|
|||
.dropzone {
|
||||
min-height: 300px;
|
||||
border: 2px dashed rgba(0, 0, 0, 0.3);
|
||||
background: none;
|
||||
padding: 20px 20px;
|
||||
}
|
||||
|
||||
|
@ -115,4 +115,9 @@ body {
|
|||
|
||||
.system-tile {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.pdf-viewer {
|
||||
width: 100%;
|
||||
height: 85vh
|
||||
}
|
|
@ -25,7 +25,7 @@ var app = {
|
|||
});
|
||||
|
||||
new ClipboardJS('.btn-clipboard');
|
||||
new Plyr($('#player'));
|
||||
new Plyr($('#player'), {ratio: '16:9'});
|
||||
|
||||
$('.footer').fadeIn(600);
|
||||
|
||||
|
|
Loading…
Reference in a new issue