Working on user registration, user disk quota, password recovery

This commit is contained in:
Sergio Brighenti 2020-02-26 18:26:19 +01:00
parent e2478f5880
commit 49c9e48e5e
21 changed files with 405 additions and 156 deletions

View file

@ -34,9 +34,10 @@ class AdminController extends Controller
$totalSize += $filesystem->getSize($media->storage_path);
}
$registerEnabled = $this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'register_enabled\'')->fetch()->value;
$hideByDefault = $this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'hide_by_default\'')->fetch()->value;
$copyUrl = $this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'copy_url_behavior\'')->fetch()->value;
$registerEnabled = $this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'register_enabled\'')->fetch()->value ?? 'off';
$hideByDefault = $this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'hide_by_default\'')->fetch()->value ?? 'off';
$copyUrl = $this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'copy_url_behavior\'')->fetch()->value ?? 'off';
$defaultUserQuota = $this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'default_user_quota\'')->fetch()->value ?? '1G';
return view()->render($response, 'dashboard/system.twig', [
'usersCount' => $usersCount,
@ -52,6 +53,7 @@ class AdminController extends Controller
'register_enabled' => $registerEnabled,
'hide_by_default' => $hideByDefault,
'copy_url_behavior' => $copyUrl,
'default_user_quota' => $defaultUserQuota,
]);
}

View file

@ -1,20 +1,21 @@
<?php
namespace App\Controllers;
namespace App\Controllers\Auth;
use App\Controllers\Controller;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class LoginController extends Controller
{
/**
* @param Response $response
* @param Response $response
*
* @throws \Twig\Error\LoaderError
* @return Response
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*
* @return Response
* @throws \Twig\Error\LoaderError
*/
public function show(Response $response): Response
{
@ -22,16 +23,20 @@ class LoginController extends Controller
return redirect($response, route('home'));
}
return view()->render($response, 'auth/login.twig');
$registerEnabled = $this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'register_enabled\'')->fetch()->value ?? 'off';
return view()->render($response, 'auth/login.twig', [
'register_enabled' => $registerEnabled,
]);
}
/**
* @param Request $request
* @param Response $response
*
* @throws \Exception
* @param Request $request
* @param Response $response
*
* @return Response
* @throws \Exception
*
*/
public function login(Request $request, Response $response): Response
{
@ -74,8 +79,8 @@ class LoginController extends Controller
}
/**
* @param Request $request
* @param Response $response
* @param Request $request
* @param Response $response
*
* @return Response
*/

View file

@ -0,0 +1,12 @@
<?php
namespace App\Controllers\Auth;
use App\Controllers\Controller;
class PasswordRecoveryController extends Controller
{
}

View file

@ -0,0 +1,120 @@
<?php
namespace App\Controllers\Auth;
use App\Controllers\Controller;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Exception\HttpNotFoundException;
class RegisterController extends Controller
{
/**
* @param Request $request
* @param Response $response
* @return Response
* @throws HttpNotFoundException
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*/
public function registerForm(Request $request, Response $response): Response
{
if ($this->session->get('logged', false)) {
return redirect($response, route('home'));
}
$registerEnabled = $this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'register_enabled\'')->fetch()->value ?? 'off';
if ($registerEnabled === 'off') {
throw new HttpNotFoundException($request);
}
return view()->render($response, 'auth/register.twig');
}
/**
* @param Request $request
* @param Response $response
* @return Response
* @throws HttpNotFoundException
* @throws \Exception
*/
public function register(Request $request, Response $response): Response
{
if ($this->session->get('logged', false)) {
return redirect($response, route('home'));
}
$registerEnabled = $this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'register_enabled\'')->fetch()->value ?? 'off';
if ($registerEnabled === 'off') {
throw new HttpNotFoundException($request);
}
if (param($request, 'email') === null && !filter_var(param($request, 'email'), FILTER_VALIDATE_EMAIL)) {
$this->session->alert(lang('email_required'), 'danger');
return redirect($response, route('register.show'));
}
if ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `email` = ?', param($request, 'email'))->fetch()->count > 0) {
$this->session->alert(lang('email_taken'), 'danger');
return redirect($response, route('register.show'));
}
if (param($request, 'username') === null) {
$this->session->alert(lang('username_required'), 'danger');
return redirect($response, route('register.show'));
}
if (param($request, 'password') === null) {
$this->session->alert(lang('password_required'), 'danger');
return redirect($response, route('register.show'));
}
if ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ?', param($request, 'username'))->fetch()->count > 0) {
$this->session->alert(lang('username_taken'), 'danger');
return redirect($response, route('register.show'));
}
do {
$userCode = humanRandomString(5);
} while ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `user_code` = ?', $userCode)->fetch()->count > 0);
$token = $this->generateUserUploadToken();
$activateToken = bin2hex(random_bytes(16));
$this->database->query('INSERT INTO `users`(`email`, `username`, `password`, `is_admin`, `active`, `user_code`, `token`, `activate_token`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [
param($request, 'email'),
param($request, 'username'),
password_hash(param($request, 'password'), PASSWORD_DEFAULT),
0,
0,
$userCode,
$token,
$activateToken,
]);
$this->session->alert(lang('register_success', [param($request, 'username')]), 'success');
$this->logger->info('New user registered.', [array_diff_key($request->getParsedBody(), array_flip(['password']))]);
return redirect($response, route('login.show'));
}
/**
* @param Request $request
* @param Response $response
* @param string $activateToken
* @return Response
*/
public function activateUser(Request $request, Response $response, string $activateToken): Response
{
}
}

View file

@ -126,4 +126,17 @@ abstract class Controller
]);
}
}
/**
* @return string
*/
protected function generateUserUploadToken(): string
{
do {
$token = 'token_'.md5(uniqid('', true));
} while ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `token` = ?', $token)->fetch()->count > 0);
return $token;
}
}

View file

@ -58,7 +58,7 @@ class DashboardController extends Controller
->search(param($request, 'search', null))
->run($page);
$copyUrl = $this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'copy_url_behavior\'')->fetch()->value;
$copyUrl = $this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'copy_url_behavior\'')->fetch()->value ?? 'off';
return view()->render(
$response,

View file

@ -66,7 +66,7 @@ class MediaController extends Controller
throw new HttpNotFoundException($request);
}
$copyUrl = $this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'copy_url_behavior\'')->fetch()->value;
$copyUrl = $this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'copy_url_behavior\'')->fetch()->value ?? 'off';
return view()->render($response, 'upload/public.twig', [
'delete_token' => $token,

View file

@ -16,8 +16,14 @@ class SettingController extends Controller
*/
public function saveSettings(Request $request, Response $response): Response
{
if (!preg_match('/[0-9]+[K|M|G|T]/i', param($request, 'default_user_quota', '1G'))) {
$this->session->alert(lang('invalid_quota', 'danger'));
return redirect($response, route('system'));
}
$this->updateSetting('register_enabled', param($request, 'register_enabled', 'off'));
$this->updateSetting('hide_by_default', param($request, 'hide_by_default', 'off'));
$this->updateSetting('default_user_quota', param($request, 'default_user_quota', '1G'));
$this->updateSetting('copy_url_behavior', param($request, 'copy_url_behavior') === null ? 'default' : 'raw');
$this->applyTheme($request);

View file

@ -101,7 +101,7 @@ class UploadController extends Controller
} while ($this->database->query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `code` = ?', $code)->fetch()->count > 0);
$published = 1;
if ($this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'hide_by_default\'')->fetch()->value === 'on') {
if (($this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'hide_by_default\'')->fetch()->value ?? 'off') === 'on') {
$published = 0;
}

View file

@ -62,7 +62,7 @@ class UserController extends Controller
*/
public function store(Request $request, Response $response): Response
{
if (param($request, 'email') === null) {
if (param($request, 'email') === null && !filter_var(param($request, 'email'), FILTER_VALIDATE_EMAIL)) {
$this->session->alert(lang('email_required'), 'danger');
return redirect($response, route('user.create'));
@ -96,7 +96,7 @@ class UserController extends Controller
$userCode = humanRandomString(5);
} while ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `user_code` = ?', $userCode)->fetch()->count > 0);
$token = $this->generateNewToken();
$token = $this->generateUserUploadToken();
$this->database->query('INSERT INTO `users`(`email`, `username`, `password`, `is_admin`, `active`, `user_code`, `token`) VALUES (?, ?, ?, ?, ?, ?, ?)', [
param($request, 'email'),
@ -151,7 +151,7 @@ class UserController extends Controller
{
$user = $this->getUser($request, $id, false);
if (param($request, 'email') === null) {
if (param($request, 'email') === null && !filter_var(param($request, 'email'), FILTER_VALIDATE_EMAIL)) {
$this->session->alert(lang('email_required'), 'danger');
return redirect($response, route('user.edit', ['id' => $id]));
@ -251,7 +251,7 @@ class UserController extends Controller
{
$user = $this->getUser($request, $id, true);
$token = $this->generateNewToken();
$token = $this->generateUserUploadToken();
$this->database->query('UPDATE `users` SET `token`=? WHERE `id` = ?', [
$token,
@ -264,16 +264,4 @@ class UserController extends Controller
return $response;
}
/**
* @return string
*/
protected function generateNewToken(): string
{
do {
$token = 'token_'.md5(uniqid('', true));
} while ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `token` = ?', $token)->fetch()->count > 0);
return $token;
}
}

View file

@ -22,7 +22,7 @@ if (!function_exists('humanFileSize')) {
for ($i = 0; ($size / 1024) > 0.9; $i++, $size /= 1024) {
}
return round($size, $precision).' '.['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][$i];
return round($size, $precision).' '.['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][$i];
}
}
@ -88,6 +88,8 @@ if (!function_exists('stringToBytes')) {
$val = (float) $val;
switch ($last) {
case 't':
$val *= 1024;
case 'g':
$val *= 1024;
case 'm':

View file

@ -2,14 +2,14 @@
// Auth routes
use App\Controllers\AdminController;
use App\Controllers\Auth\RegisterController;
use App\Controllers\ClientController;
use App\Controllers\DashboardController;
use App\Controllers\ExportController;
use App\Controllers\LoginController;
use App\Controllers\Auth\LoginController;
use App\Controllers\MediaController;
use App\Controllers\ProfileController;
use App\Controllers\SettingController;
use App\Controllers\ThemeController;
use App\Controllers\UpgradeController;
use App\Controllers\UploadController;
use App\Controllers\UserController;
@ -63,6 +63,8 @@ $app->group('', function (RouteCollectorProxy $group) {
})->add(App\Middleware\CheckForMaintenanceMiddleware::class)->add(AuthMiddleware::class);
$app->get('/', [DashboardController::class, 'redirects'])->setName('root');
$app->get('/register', [RegisterController::class, 'registerForm'])->setName('register.show');
$app->post('/register', [RegisterController::class, 'register'])->setName('register');
$app->get('/login', [LoginController::class, 'show'])->setName('login.show');
$app->post('/login', [LoginController::class, 'login'])->setName('login');
$app->map(['GET', 'POST'], '/logout', [LoginController::class, 'logout'])->setName('logout');

View file

@ -1,119 +1,124 @@
<?php
return [
'lang' => 'English',
'enforce_language' => 'Enforce language',
'yes' => 'Yes',
'no' => 'No',
'send' => 'Send',
'no_media' => 'No media found.',
'login.username' => 'Username or E-Mail',
'password' => 'Password',
'login' => 'Login',
'username' => 'Username',
'home' => 'Home',
'users' => 'Users',
'system' => 'System',
'profile' => 'Profile',
'logout' => 'Logout',
'pager.next' => 'Next',
'pager.previous' => 'Previous',
'copy_link' => 'Copy link',
'public.telegram' => 'Share on Telegram',
'public.delete_text' => 'Are you sure you want to delete this item? It will be gone forever!',
'preview' => 'Preview',
'filename' => 'Filename',
'size' => 'Size',
'public' => 'Public',
'owner' => 'Owner',
'date' => 'Date',
'raw' => 'Show raw',
'download' => 'Download',
'upload' => 'Upload',
'delete' => 'Delete',
'publish' => 'Publish',
'hide' => 'Hide',
'files' => 'Files',
'orphaned_files' => 'Orphaned Files',
'theme' => 'Theme',
'click_to_load' => 'Click to load...',
'apply' => 'Apply',
'save' => 'Save',
'used' => 'Used',
'php_info' => 'PHP Informations',
'system_settings' => 'System Settings',
'user.create' => 'Create User',
'user.edit' => 'Edit User',
'is_active' => 'Is active',
'is_admin' => 'Is administrator',
'your_profile' => 'Your Profile',
'token' => 'Token',
'copy' => 'Copy',
'update' => 'Update',
'edit' => 'Edit',
'client_config' => 'Client Configuration',
'user_code' => 'User Code',
'active' => 'Active',
'admin' => 'Admin',
'reg_date' => 'Registration Date',
'none' => 'None',
'open' => 'Open',
'confirm' => 'Confirmation',
'confirm_string' => 'Are you sure?',
'installed' => 'Installation completed successfully!',
'bad_login' => 'Wrong credentials.',
'account_disabled' => 'Your account is disabled.',
'welcome' => 'Welcome, %s!',
'goodbye' => 'Goodbye!',
'token_not_found' => 'Token specified not found.',
'email_required' => 'The email is required.',
'email_taken' => 'The email is already taken.',
'username_required' => 'The username is required.',
'username_taken' => 'The username is already taken.',
'password_required' => 'The password is required.',
'user_created' => 'User "%s" created!',
'user_updated' => 'User "%s" updated!',
'profile_updated' => 'Profile updated successfully!',
'user_deleted' => 'User deleted.',
'cannot_delete' => 'You cannot delete yourself.',
'cannot_demote' => 'You cannot demote yourself.',
'cannot_write_file' => 'The destination path is not writable.',
'deleted_orphans' => 'Successfully deleted %d orphaned files.',
'switch_to' => 'Switch to',
'gallery' => 'Gallery',
'table' => 'Table',
'dotted_search' => 'Search...',
'order_by' => 'Order by...',
'time' => 'Time',
'name' => 'Name',
'maintenance' => 'Maintenance',
'clean_orphaned_uploads' => 'Clean Orphaned Uploads',
'path_not_writable' => 'The output path is not writable.',
'already_latest_version' => 'You already have the latest version.',
'new_version_available' => 'New version %s available!',
'cannot_retrieve_file' => 'Cannot retrieve the file.',
'file_size_no_match' => 'The downloaded file doesn\'t match the correct file size.',
'check_for_updates' => 'Check for updates',
'upgrade' => 'Upgrade',
'updates' => 'Updates',
'lang' => 'English',
'enforce_language' => 'Enforce language',
'yes' => 'Yes',
'no' => 'No',
'send' => 'Send',
'no_media' => 'No media found.',
'login.username' => 'Username or E-Mail',
'password' => 'Password',
'login' => 'Login',
'username' => 'Username',
'home' => 'Home',
'users' => 'Users',
'system' => 'System',
'profile' => 'Profile',
'logout' => 'Logout',
'pager.next' => 'Next',
'pager.previous' => 'Previous',
'copy_link' => 'Copy link',
'public.telegram' => 'Share on Telegram',
'public.delete_text' => 'Are you sure you want to delete this item? It will be gone forever!',
'preview' => 'Preview',
'filename' => 'Filename',
'size' => 'Size',
'public' => 'Public',
'owner' => 'Owner',
'date' => 'Date',
'raw' => 'Show raw',
'download' => 'Download',
'upload' => 'Upload',
'delete' => 'Delete',
'publish' => 'Publish',
'hide' => 'Hide',
'files' => 'Files',
'orphaned_files' => 'Orphaned Files',
'theme' => 'Theme',
'click_to_load' => 'Click to load...',
'apply' => 'Apply',
'save' => 'Save',
'used' => 'Used',
'php_info' => 'PHP Informations',
'system_settings' => 'System Settings',
'user.create' => 'Create User',
'user.edit' => 'Edit User',
'is_active' => 'Is active',
'is_admin' => 'Is administrator',
'your_profile' => 'Your Profile',
'token' => 'Token',
'copy' => 'Copy',
'update' => 'Update',
'edit' => 'Edit',
'client_config' => 'Client Configuration',
'user_code' => 'User Code',
'active' => 'Active',
'admin' => 'Admin',
'reg_date' => 'Registration Date',
'none' => 'None',
'open' => 'Open',
'confirm' => 'Confirmation',
'confirm_string' => 'Are you sure?',
'installed' => 'Installation completed successfully!',
'bad_login' => 'Wrong credentials.',
'account_disabled' => 'Your account is disabled.',
'welcome' => 'Welcome, %s!',
'goodbye' => 'Goodbye!',
'token_not_found' => 'Token specified not found.',
'email_required' => 'The email is required.',
'email_taken' => 'The email is already taken.',
'username_required' => 'The username is required.',
'username_taken' => 'The username is already taken.',
'password_required' => 'The password is required.',
'user_created' => 'User "%s" created!',
'user_updated' => 'User "%s" updated!',
'profile_updated' => 'Profile updated successfully!',
'user_deleted' => 'User deleted.',
'cannot_delete' => 'You cannot delete yourself.',
'cannot_demote' => 'You cannot demote yourself.',
'cannot_write_file' => 'The destination path is not writable.',
'deleted_orphans' => 'Successfully deleted %d orphaned files.',
'switch_to' => 'Switch to',
'gallery' => 'Gallery',
'table' => 'Table',
'dotted_search' => 'Search...',
'order_by' => 'Order by...',
'time' => 'Time',
'name' => 'Name',
'maintenance' => 'Maintenance',
'clean_orphaned_uploads' => 'Clean Orphaned Uploads',
'path_not_writable' => 'The output path is not writable.',
'already_latest_version' => 'You already have the latest version.',
'new_version_available' => 'New version %s available!',
'cannot_retrieve_file' => 'Cannot retrieve the file.',
'file_size_no_match' => 'The downloaded file doesn\'t match the correct file size.',
'check_for_updates' => 'Check for updates',
'upgrade' => 'Upgrade',
'updates' => 'Updates',
'maintenance_in_progress' => 'Platform under maintenance, try again later...',
'cancel' => 'Cancel',
'auto_set' => 'Set automatically',
'default_lang_behavior' => 'XBackBone will try to match the browser language by default (the fallback is English).',
'prerelease_channel' => 'Prerelease Channel',
'no_upload_token' => 'You don\'t have a personal upload token. (Generate one and try again.)',
'drop_to_upload' => 'Click or drop your files here to upload.',
'donation' => 'Donation',
'donate_text' => 'If you like XBackBone, consider a donation to support development!',
'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 HTML head applied.',
'remember_me' => 'Remember me',
'please_wait' => 'Please wait…',
'dont_close' => 'Do not close this tab until completion.',
'register_enabled' => 'Registration Enabled',
'hide_by_default' => 'Hide uploads by default',
'copy_url_behavior' => 'Copy URL mode',
'settings_saved' => 'System settings saved!',
'export_data' => 'Export Data'
'cancel' => 'Cancel',
'auto_set' => 'Set automatically',
'default_lang_behavior' => 'XBackBone will try to match the browser language by default (the fallback is English).',
'prerelease_channel' => 'Prerelease Channel',
'no_upload_token' => 'You don\'t have a personal upload token. (Generate one and try again.)',
'drop_to_upload' => 'Click or drop your files here to upload.',
'donation' => 'Donation',
'donate_text' => 'If you like XBackBone, consider a donation to support development!',
'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 HTML head applied.',
'remember_me' => 'Remember me',
'please_wait' => 'Please wait…',
'dont_close' => 'Do not close this tab until completion.',
'register_enabled' => 'Registrations enabled',
'hide_by_default' => 'Hide media by default',
'copy_url_behavior' => 'Copy URL mode',
'settings_saved' => 'System settings saved!',
'export_data' => 'Export Data',
'password_recovery' => 'Recover password',
'no_account' => 'Don\'t have an account?',
'register' => 'Register',
'default_user_quota' => 'Default User Quota',
'invalid_quota' => 'Invalid values as default user quota.',
];

View file

@ -0,0 +1,7 @@
ALTER TABLE `users`
ADD COLUMN `activate_token` VARCHAR(32) DEFAULT NULL,
ADD COLUMN `reset_token` VARCHAR(32) DEFAULT NULL,
ADD COLUMN `disk_quota` BIGINT(20) NOT NULL DEFAULT -1;
ALTER TABLE `users` ADD INDEX (`activate_token`);
ALTER TABLE `users` ADD INDEX (`reset_token`);

View file

@ -0,0 +1,10 @@
ALTER TABLE `users` ADD COLUMN `activate_token` VARCHAR(32);
ALTER TABLE `users` ADD COLUMN `reset_token` VARCHAR(32);
ALTER TABLE `users` ADD COLUMN `disk_quota` BIGINT NOT NULL DEFAULT -1;
CREATE INDEX IF NOT EXISTS `activate_token_index`
ON `users` (`activate_token`);
CREATE INDEX IF NOT EXISTS `reset_token_index`
ON `users` (`reset_token`);

View file

@ -35,19 +35,28 @@
</div>
<div class="row">
<div class="col-md-12">
<label for="inputEmail" class="sr-only">{{ lang('login.username') }}</label>
<label for="username" class="sr-only">{{ lang('login.username') }}</label>
<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 class="d-flex justify-content-between">
<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>
<a href="#" class="">{{ lang('password_recovery') }}</a>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-md-12">
<button class="btn btn-lg btn-primary btn-block" type="submit">{{ lang('login') }}</button>
{% if register_enabled == 'on' %}
<div class="text-center mt-2">
{{ lang('no_account') }} <a href="{{ route('register.show') }}">{{ lang('register') }}</a>.
</div>
{% endif %}
</div>
</div>
</form>

View file

@ -0,0 +1,56 @@
{% extends 'base.twig' %}
{% block title %}{{ lang('login') }}{% endblock %}
{% block head %}
<style>
html {
height: 100%;
}
body {
height: 100%;
display: -ms-flexbox;
display: -webkit-box;
display: flex;
-ms-flex-align: center;
-ms-flex-pack: center;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: center;
justify-content: center;
padding-bottom: 40px;
margin-bottom: 0;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid">
<form class="form-signin" method="post" action="{{ route('register') }}">
<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' %}
</div>
</div>
<div class="row">
<div class="col-md-12">
<label for="username" class="sr-only">{{ lang('username') }}</label>
<input type="text" id="username" class="form-control" placeholder="{{ lang('username') }}" name="username" required autofocus>
<label for="email" class="sr-only">E-Mail</label>
<input type="email" id="email" class="form-control" placeholder="mail@example.com" name="password" required>
<label for="password" class="sr-only">{{ lang('password') }}</label>
<input type="password" id="password" class="form-control" placeholder="{{ lang('password') }}" name="password" required>
</div>
</div>
<div class="row mt-2">
<div class="col-md-12">
<button class="btn btn-lg btn-primary btn-block" type="submit">{{ lang('register') }}</button>
<div class="text-center mt-2">
<a href="{{ route('login.show') }}">{{ lang('cancel') }}</a>
</div>
</div>
</div>
</form>
</div>
{% endblock %}

View file

@ -37,7 +37,7 @@
<div class="dropdown-menu shadow-sm" aria-labelledby="userDropdown">
<a class="dropdown-item disabled" href="javascript:void(0)">{{ lang('used') }}: {{ session.get('used_space') }}</a>
{% if session.get('admin') %}
<a class="dropdown-item" href="{{ route('switchView') }}"><i class="fas fa-fw fa-sync"></i> {{ lang('switch_to') }}: {{ session.gallery_view is null or session.gallery_view ? lang('gallery') : lang('table') }}</a>
<a class="dropdown-item" href="{{ route('switchView') }}"><i class="fas fa-fw fa-sync"></i> {{ lang('switch_to') }}: {{ session.get('gallery_view') is null or session.get('gallery_view') ? lang('gallery') : lang('table') }}</a>
{% endif %}
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="{{ route('profile') }}"><i class="fas fa-fw fa-user"></i> {{ lang('profile') }}</a>

View file

@ -90,6 +90,13 @@
</div>
</div>
<div class="form-group row">
<label for="default_user_quota" class="col-sm-4 col-form-label">{{ lang('default_user_quota') }}</label>
<div class="col-sm-8">
<input type="text" class="form-control" id="default_user_quota" name="default_user_quota" pattern="[0-9]+[K|M|G|T]" title="512M, 2G, 1T, ..." placeholder="1G" value="{{ default_user_quota }}">
</div>
</div>
<div class="form-group row">
<label for="custom_head" class="col-sm-4 col-form-label">{{ lang('enforce_language') }}</label>
<div class="col-sm-8">

View file

@ -34,6 +34,11 @@ body {
border-bottom-left-radius: 0;
}
.form-signin input[type="email"] {
margin-bottom: -1px;
border-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: .50rem;
border-top-left-radius: 0;

View file

@ -106,7 +106,7 @@ var app = {
window.open($('#telegram-share-button').data('url') + $('#telegram-share-text').val(), '_blank');
},
checkForUpdates: function () {
$('#checkForUpdatesMessage').empty().html('<i class="fas fa-sync fa-spin"></i>');
$('#checkForUpdatesMessage').empty().html('<i class="fas fa-spinner fa-pulse fa-3x"></i>');
$('#doUpgradeButton').prop('disabled', true);
$.get(window.AppConfig.base_url + '/system/checkForUpdates?prerelease=' + $(this).data('prerelease'), function (data) {
$('#checkForUpdatesMessage').empty().text(data.message);