1
0
mirror of https://github.com/typemill/typemill.git synced 2025-08-01 11:50:28 +02:00

Add auth code feature, add simpleMail model and refactor system settings

This commit is contained in:
trendschau
2023-12-26 04:39:17 +01:00
parent d4dedecfd1
commit 9978638266
13 changed files with 616 additions and 140 deletions

View File

@@ -7,6 +7,7 @@ use Psr\Http\Message\ResponseInterface as Response;
use Slim\Routing\RouteContext;
use Typemill\Models\Validation;
use Typemill\Models\User;
use Typemill\Models\SimpleMail;
use Typemill\Static\Translations;
class ControllerWebAuth extends Controller
@@ -21,68 +22,215 @@ class ControllerWebAuth extends Controller
public function login(Request $request, Response $response)
{
/*
if( ( null !== $request->getattribute('csrf_result') ) OR ( $request->getattribute('csrf_result') === false ) )
{
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'));
}
*/
$input = $request->getParsedBody();
$validation = new Validation();
# $settings = $this->c->get('settings');
$securitylog = $this->settings['securitylog'] ?? false;
$authcodeactive = $this->settings['authcode'] ?? false;
if($validation->signin($input) === true)
if($validation->signin($input) !== true)
{
$user = new User();
if(!$user->setUserWithPassword($input['username']))
if($securitylog)
{
$this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.'));
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
\Typemill\Static\Helpers::addLogEntry('login: invalid data');
}
$userdata = $user->getUserData();
$this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.'));
if($userdata && password_verify($input['password'], $userdata['password']))
{
# check if user has confirmed the account
if(isset($userdata['optintoken']) && $userdata['optintoken'])
{
$this->c->get('flash')->addMessage('error', Translations::translate('Your registration is not confirmed yet. Please check your e-mails and use the confirmation link.'));
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
}
$user->login();
# return $response->withHeader('Location', $this->routeParser->urlFor('settings.show'))->withStatus(302);
# if user is allowed to view content-area
$acl = $this->c->get('acl');
if($acl->hasRole($userdata['userrole']) && $acl->isAllowed($userdata['userrole'], 'content', 'view'))
{
$editor = (isset($this->settings['editor']) && $this->settings['editor'] == 'visual') ? 'visual' : 'raw';
return $response->withHeader('Location', $this->routeParser->urlFor('content.' . $editor))->withStatus(302);
}
return $response->withHeader('Location', $this->routeParser->urlFor('user.account'))->withStatus(302);
}
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
}
if(isset($this->settings['securitylog']) && $this->settings['securitylog'])
$user = new User();
if(!$user->setUserWithPassword($input['username']))
{
\Typemill\Static\Helpers::addLogEntry('wrong login');
if($securitylog)
{
\Typemill\Static\Helpers::addLogEntry('login: user not found');
}
$this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.'));
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
}
$this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.'));
$userdata = $user->getUserData();
$authcodedata = $this->checkAuthcode($userdata);
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
if($userdata && !password_verify($input['password'], $userdata['password']))
{
if($securitylog)
{
\Typemill\Static\Helpers::addLogEntry('login: wrong password');
}
# always show authcode page, so attacker does not know if email or password was wrong or mail was send.
if($authcodeactive && !$authcodedata['valid'])
{
# a bit slower because send mail takes some time usually
usleep(rand(100000, 200000));
# show authcode page
return $this->c->get('view')->render($response, 'auth/authcode.twig', [
'username' => $userdata['username'],
]);
}
$this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.'));
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
}
# check device fingerprint
if($authcodeactive)
{
$fingerprint = $this->generateDeviceFingerprint();
if(!$this->findDeviceFingerprint($fingerprint, $userdata))
{
# invalidate authcodedata so user has to use a new authcode again
$authcodedata['valid'] = false;
$authcodedata['validated'] = 12345;
}
}
if($authcodeactive && !$authcodedata['valid'] )
{
# generate new authcode
$authcodevalue = rand(10000, 99999);
$mail = new SimpleMail($settings);
$subject = Translations::translate('Your authentication code for Typemill');
$message = Translations::translate('Use the following authentication code to login into Typemill cms') . ': ' . $authcodevalue;
$send = $mail->send($userdata['email'], $subject, $message);
$send = true;
if(!$send)
{
$title = Translations::translate('Error sending email');
$message = Translations::translate('Dear ') . $userdata['username'] . ', ' . Translations::translate('we could not send the email with the authentication code to your address. Reason: ') . $mail->error;
}
else
{
# store authcode
$user->setValue('authcodedata', $authcodevalue . ':' . time() . ':' . $authcodedata['validated']);
$user->updateUser();
}
# show authcode page
return $this->c->get('view')->render($response, 'auth/authcode.twig', [
'username' => $userdata['username'],
]);
}
# check if user has confirmed the account
if(isset($userdata['optintoken']) && $userdata['optintoken'])
{
if($securitylog)
{
\Typemill\Static\Helpers::addLogEntry('login: user not confirmed yet.');
}
$this->c->get('flash')->addMessage('error', Translations::translate('Your registration is not confirmed yet. Please check your e-mails and use the confirmation link.'));
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
}
$user->login();
# return $response->withHeader('Location', $this->routeParser->urlFor('settings.show'))->withStatus(302);
# if user is allowed to view content-area
$acl = $this->c->get('acl');
if($acl->hasRole($userdata['userrole']) && $acl->isAllowed($userdata['userrole'], 'content', 'view'))
{
$editor = (isset($this->settings['editor']) && $this->settings['editor'] == 'visual') ? 'visual' : 'raw';
return $response->withHeader('Location', $this->routeParser->urlFor('content.' . $editor))->withStatus(302);
}
return $response->withHeader('Location', $this->routeParser->urlFor('user.account'))->withStatus(302);
}
# login user with valid authcode
public function loginWithAuthcode(Request $request, Response $response)
{
$input = $request->getParsedBody();
$validation = new Validation();
$securitylog = $this->settings['securitylog'] ?? false;
if($validation->authcode($input) !== true)
{
if($securitylog)
{
\Typemill\Static\Helpers::addLogEntry('login: invalid authcode format');
}
$this->c->get('flash')->addMessage('error', Translations::translate('Invalid authcode format, please try again.'));
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
}
$user = new User();
if(!$user->setUserWithPassword($input['username']))
{
if($securitylog)
{
\Typemill\Static\Helpers::addLogEntry('login: user not found');
}
$this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.'));
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
}
$userdata = $user->getUserData();
$authcodevalue = $input['code-1'] . $input['code-2'] . $input['code-3'] . $input['code-4'] . $input['code-5'];
$authcodedata = $this->checkAuthcode($userdata);
if(!$this->validateAuthcode($authcodevalue, $authcodedata))
{
if($securitylog)
{
\Typemill\Static\Helpers::addLogEntry('login: authcode wrong or outdated.');
}
$this->c->get('flash')->addMessage('error', Translations::translate('The authcode was wrong or outdated, please start again.'));
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
}
# add the device fingerprint if not set yet
$fingerprints = $userdata['fingerprints'] ?? [];
$fingerprint = $this->generateDeviceFingerprint();
if(!$this->findDeviceFingerprint($fingerprint, $fingerprints))
{
$fingerprints[] = $fingerprint;
$user->setValue('fingerprints', $fingerprints);
}
# update authcode lastValidation and store
$user->setValue('authcodedata', $authcodevalue . ':' . $authcodedata['generated'] . ':' . time());
$user->updateUser();
$user->login();
# if user is allowed to view content-area
$acl = $this->c->get('acl');
if($acl->hasRole($userdata['userrole']) && $acl->isAllowed($userdata['userrole'], 'content', 'view'))
{
$editor = (isset($this->settings['editor']) && $this->settings['editor'] == 'visual') ? 'visual' : 'raw';
return $response->withHeader('Location', $this->routeParser->urlFor('content.' . $editor))->withStatus(302);
}
return $response->withHeader('Location', $this->routeParser->urlFor('user.account'))->withStatus(302);
}
/**
* log out a user
*
@@ -97,4 +245,106 @@ class ControllerWebAuth extends Controller
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
}
# check if the stored authcode in userdata is valid and/or fresh
private function checkAuthcode($userdata)
{
# format: 12345:time(generated):time(validated)
$authcodedata = $userdata['authcodedata'] ?? false;
if(!$authcodedata)
{
return $authcode = [
'value' => 12345,
'generated' => 12345,
'validated' => 12345,
'valid' => false,
'fresh' => false
];
}
$validation = new Validation();
$authcodedata = explode(":", $authcodedata);
# validate format here, do we need it?
$now = time();
$lastValidation = 60 * 60 * 24;
$lastGeneration = 60 * 5;
$valid = false;
$fresh = false;
# if last validation is less than 24 hours old
if($now - $lastValidation < $authcodedata[2])
{
$valid = true;
}
# if last generation is less than 5 minutes old
if($now - $lastGeneration < $authcodedata[1])
{
$fresh = true;
}
$authcode = [
'value' => $authcodedata[0],
'generated' => $authcodedata[1],
'validated' => $authcodedata[2],
'valid' => $valid,
'fresh' => $fresh
];
return $authcode;
}
# check if the submitted authcode is the same as the stored authcode
private function validateAuthcode($authcodevalue, $authcodedata)
{
if($authcodedata['valid'] === true)
{
return true;
}
if($authcodedata['fresh'] === false)
{
return false;
}
if($authcodevalue == $authcodedata['value'])
{
return true;
}
return false;
}
# create a simple device fingerprint
private function generateDeviceFingerprint()
{
$userAgent = $_SERVER['HTTP_USER_AGENT'];
$ipAddress = $_SERVER['REMOTE_ADDR'];
$acceptLanguage = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : '';
$fingerprint = md5($userAgent . $ipAddress . $acceptLanguage);
return $fingerprint;
}
# create a simple device fingerprint
private function findDeviceFingerprint($fingerprint, $userdata)
{
if(!isset($userdata['fingerprints']) or empty($userdata['fingerprints']))
{
return false;
}
if(!in_array($fingerprint, $userdata['fingerprints']))
{
return false;
}
return true;
}
}

View File

@@ -7,6 +7,7 @@ use Psr\Http\Message\ResponseInterface as Response;
use Slim\Routing\RouteContext;
use Typemill\Models\User;
use Typemill\Models\Validation;
use Typemill\Models\SimpleMail;
use Typemill\Static\Translations;
use Typemill\Extensions\ParsedownExtension;
@@ -50,15 +51,9 @@ class ControllerWebRecover extends Controller
$link = '<a href="'. $url . '">' . $url . '</a>';
# define the headers
$headers = 'Content-Type: text/html; charset=utf-8' . "\r\n";
$headers .= 'Content-Transfer-Encoding: base64' . "\r\n";
if(isset($settings['recoverfrom']) && $settings['recoverfrom'] != '')
{
$headers .= 'From: ' . $settings['recoverfrom'];
}
$mail = new SimpleMail($settings);
$subjectline = (isset($settings['recoversubject']) && ($settings['recoversubject'] != '') ) ? $settings['recoversubject'] : 'Recover your password';
$subject = '=?UTF-8?B?' . base64_encode($subjectline) . '?=';
$subject = (isset($settings['recoversubject']) && ($settings['recoversubject'] != '') ) ? $settings['recoversubject'] : 'Recover your password';
$messagetext = Translations::translate('Dear user');
$messagetext .= ",<br/><br/>";
@@ -72,16 +67,14 @@ class ControllerWebRecover extends Controller
$messagetext = $parsedown->markup($contentArray);
}
$message = base64_encode($messagetext . "<br/><br/>" . $link);
$message = $messagetext . "<br/><br/>" . $link;
# $send = mail($requiredUser['email'], $subject, $message, $headers);
$send = $mail->send($requiredUser['email'], $subject, $message);
$send = false;
if($send == 'delete')
if(!$send)
{
$title = Translations::translate('Error sending email');
$message = Translations::translate('Dear ') . $requiredUser['username'] . ', ' . Translations::translate('we could not send the email with the password instructions to your address. Please contact the website owner and ask for help.');
$message = Translations::translate('Dear ') . $requiredUser['username'] . ', ' . Translations::translate('we could not send the email with the password instructions to your address. Reason: ') . $mail->error;
}
else
{

View File

@@ -95,7 +95,11 @@ class ControllerWebSetup extends Controller
# create initial settings file
$settingsModel = new Settings();
$settingsModel->createSettings();
$settingsModel->createSettings([
'author' => $params['username'],
'mailfrom' => $params['email'],
'mailfromname' => $params['username']
]);
$urlinfo = $this->c->get('urlinfo');
$route = $urlinfo['baseurl'] . '/tm/system';

View File

@@ -224,18 +224,26 @@ class Settings
return $settingsDefinitions;
}
public function createSettings()
public function createSettings(array $defaultSettings = NULL)
{
$language = Translations::whichLanguage();
$defaults = [
'language' => Translations::whichLanguage()
];
if($defaultSettings)
{
$defaults = array_merge($defaults, $defaultSettings);
}
$initialSettings = $this->storage->updateYaml('settingsFolder', '', 'settings.yaml', [
'language' => $language
$defaults
]);
if($initialSettings)
{
return true;
}
return false;
}

View File

@@ -0,0 +1,63 @@
<?php
namespace Typemill\Models;
class SimpleMail
{
private $from = false;
private $reply = false;
public $error;
public function __construct($settings)
{
if(isset($settings['mailfrom']) && $settings['mailfrom'] != '')
{
$this->from = trim($settings['mailfrom']);
if(isset($settings['mailfromname']) && $settings['mailfromname'] != '')
{
$this->from = '=?UTF-8?B?' . base64_encode($settings['mailfromname']) . '?= <' . trim($settings['mailfrom']) . '>';
}
}
if(isset($settings['mailreply']) && $settings['mailreply'] != '')
{
$this->reply = trim($settings['mailreply']);
}
}
public function sendEmail(string $to, string $subject, string $message)
{
if(!$this->from)
{
$this->error = 'You need to add a email address into the settings.';
return false;
}
# 'Reply-To: webmaster@example.com' . "\r\n" .
$headers = 'Content-Type: text/html; charset=utf-8' . "\r\n";
$headers .= 'Content-Transfer-Encoding: base64' . "\r\n";
$headers .= 'From: ' . $this->from . "\r\n";
if($this->$reply)
{
$headers .= 'Reply-To: base64' . $this->reply . "\r\n";
}
$headers .= 'X-Mailer: PHP/' . phpversion();
$subject = '=?UTF-8?B?' . base64_encode($subject) . '?=';
$message = base64_encode($message);
$send = mail($to, $subject, $message, $headers);
if($send !== true)
{
$this->error = error_get_last()['message'];
}
return $send;
}
}

View File

@@ -274,6 +274,32 @@ class Validation
return false;
}
/**
* validation for authcode confirmation
*
* @param array $params with form data.
* @return obj $v the validation object passed to a result method.
*/
public function authcode(array $params)
{
$v = new Validator($params);
$v->rule('required', ['username', 'code-1', 'code-2', 'code-3', 'code-4', 'code-5'])->message("Required");
$v->rule('alphaNum', 'username')->message("Invalid characters");
$v->rule('regex', 'code-1', '/^[0-9]{1}$/')->message("Must be 1-9");
$v->rule('regex', 'code-2', '/^[0-9]{1}$/')->message("Must be 1-9");
$v->rule('regex', 'code-3', '/^[0-9]{1}$/')->message("Must be 1-9");
$v->rule('regex', 'code-4', '/^[0-9]{1}$/')->message("Must be 1-9");
$v->rule('regex', 'code-5', '/^[0-9]{1}$/')->message("Must be 1-9");
if($v->validate())
{
return true;
}
return false;
}
/**
* validation for setup user (in backoffice)

View File

@@ -194,23 +194,6 @@ abstract class Plugin implements EventSubscriberInterface
$this->container->get('assets')->addJS($JS);
}
/*
protected function addEditorJS($JS)
{
$this->container->get('assets')->addEditorJS($JS);
}
protected function addEditorInlineJS($JS)
{
$this->container->get('assets')->addEditorInlineJS($JS);
}
protected function addEditorCSS($CSS)
{
$this->container->get('assets')->addEditorCSS($CSS);
}
*/
protected function addInlineJS($JS)
{
$this->container->get('assets')->addInlineJS($JS);

View File

@@ -51,7 +51,6 @@ class Helpers{
return $ip;
}
public static function addLogEntry($action)
{
$line = self::getUserIP();

View File

@@ -0,0 +1,103 @@
{% extends 'layouts/layoutAuth.twig' %}
{% block title %}Login Authentication Code{% endblock %}
{% block content %}
<div class="lg:flex-row flex flex-col-reverse justify-end">
<div class="lg:w-1/2 bg-teal-600 text-white min-h-screen flex justify-center items-center">
<div class="max-w-md content-center">
<h1 class="text-4xl py-5">Authentication Code</h1>
<p>Enter the auth code from the e-mail you got:</a>
<form method="POST" action="{{ url_for("auth.authcode") }}" autocomplete="off">
<fieldset>
<div class="flex justify-between my-2">
<input
type="text"
name="code-1"
pattern="[0-9]"
maxlength="1"
oninput="moveToNextField(this)"
class="mr-2 form-control block w-full px-3 py-3 text-xl text-center font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-non"
required>
<input
type="text"
name="code-2"
pattern="[0-9]"
maxlength="1"
oninput="moveToNextField(this)"
class="mr-2 form-control block w-full px-3 py-3 text-xl text-center font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-non"
required>
<input
type="text"
name="code-3"
pattern="[0-9]"
maxlength="1"
oninput="moveToNextField(this)"
class="mr-2 form-control block w-full px-3 py-3 text-xl text-center font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-non"
required>
<input
type="text"
name="code-4"
pattern="[0-9]"
maxlength="1"
oninput="moveToNextField(this)"
class="mr-2 form-control block w-full px-3 py-3 text-xl text-center font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-non"
required>
<input
type="text"
name="code-5"
pattern="[0-9]"
maxlength="1"
oninput="moveToNextField(this)"
class="mr-2 form-control block w-full px-3 py-3 text-xl text-center font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-non"
required>
</div>
<input type="hidden" value="{{username}}" name="username">
<div class="personal-mail hidden">
<label>Personal Mail</label>
<input type="text" name="personal-honey-mail">
</div>
<input
type="submit"
value="{{ translate('Confirm') }}"
class="block w-full mt-6 px-3 py-3 border-0 font-medium text-xs leading-tight uppercase border-2 border-stone-50 text-stone-50 pointer hover:bg-stone-50 hover:text-stone-800 cursor-pointer focus:outline-none focus:ring-0 transition duration-100 ease-in-out"
/>
</fieldset>
<script>
function moveToNextField(currentField) {
var inputValue = currentField.value;
var inputLength = inputValue.length;
if (inputLength === 1)
{
var nextField = currentField.nextElementSibling;
if (nextField)
{
nextField.focus();
}
}
}
</script>
</form>
</div>
</div>
<div class="lg:w-1/2 lg:bg-white lg:text-black p-5 bg-teal-600 text-white border-b border-white content-center flex justify-center items-center">
<div class="max-w-md content-center">
<h2 class="text-4xl py-5">{{ translate('Auth code missing?') }}</h2>
<p>{{ translate('If you did not receive an email with an authentication code, then the username or password you entered was wrong. Please try again.') }}</p>
</div>
</div>
</div>
{% endblock %}

View File

@@ -18,6 +18,11 @@ $app->group('/tm', function (RouteCollectorProxy $group) use ($settings) {
$group->get('/login', ControllerWebAuth::class . ':show')->setName('auth.show');
$group->post('/login', ControllerWebAuth::class . ':login')->setName('auth.login');
if(isset($settings['authcode']) && $settings['authcode'])
{
$group->post('/authcode', ControllerWebAuth::class . ':loginWithAuthcode')->setName('auth.authcode');
}
if(isset($settings['recoverpw']) && $settings['recoverpw'])
{
$group->get('/recover', ControllerWebRecover::class . ':showRecoverForm')->setName('auth.recoverform');
@@ -50,6 +55,7 @@ $app->group('/tm', function (RouteCollectorProxy $group) use ($routeParser,$acl)
$app->redirect('/tm', $routeParser->urlFor('auth.show'), 302);
$app->redirect('/tm/', $routeParser->urlFor('auth.show'), 302);
$app->redirect('/tm/authcode', $routeParser->urlFor('auth.show'), 302);
# downloads
$app->get('/media/files[/{params:.*}]', ControllerWebDownload::class . ':download')->setName('download.file');

View File

@@ -85,6 +85,7 @@ meta:
reference:
type: text
label: Reference to page
placeholder: '/path/to/internal/page or https://exgernal-page.org'
maxlength: 200
referencetype:
type: radio

View File

@@ -4,19 +4,20 @@ fieldsetsystem:
fields:
title:
type: text
label: Website title
label: 'Website title'
maxlength: 60
css: lg:w-half
author:
type: text
label: Website owner
label: 'Website owner'
css: lg:w-half
maxlength: 60
copyright:
type: select
label: Copyright
label: 'Copyright'
css: lg:w-half
maxlength: 60
description: 'Used for copyright and year in footer.'
options:
'©': '©'
'CC-BY': 'CC-BY'
@@ -30,11 +31,13 @@ fieldsetsystem:
label: Year
css: lg:w-half
maxlength: 4
description: 'Used for copyright and year in footer.'
language:
type: select
label: Language (author area)
label: 'Language (author area)'
css: lg:w-half
maxlength: 60
description: 'Used for translations in author area, themes, and plugins.'
options:
'en': 'English'
'ru': 'Russian'
@@ -44,15 +47,16 @@ fieldsetsystem:
'fr': 'French'
langattr:
type: text
label: Language attribute (website)
label: 'Language attribute (website)'
css: lg:w-half
maxlength: 5
description: Please use ISO 639-1 codes like "en"
description: 'Used for frontend language attribute. Please use ISO 639-1 codes like "en".'
sitemap:
type: text
label: Google sitemap (readonly)
label: 'Google sitemap (readonly)'
css: lg:w-half
disabled: true
description: 'Submit the url above in google search console to support indexing.'
fieldsetmedia:
type: fieldset
legend: Media
@@ -63,48 +67,48 @@ fieldsetmedia:
favicon:
type: image
label: Favicon
description: Only PNG format will work.
description: 'Only PNG format will work.'
liveimagewidth:
type: number
label: Standard width for live pictures
label: 'Standard width for live pictures'
placeholder: 820
description: Default width of live images is 820px. Changes will apply to future uploads.
description: 'Default width of live images is 820px. Changes will apply to future uploads.'
css: lg:w-half
liveimageheight:
type: number
label: Standard height for live pictures
description: If you add a value for the height, then the image will be cropped.
label: 'Standard height for live pictures'
description: 'If you add a value for the height, then the image will be cropped.'
css: lg:w-half
maximageuploads:
type: number
label: Maximum size for image uploads in MB
description: The maximum image size might be limited by your server settings.
label: 'Maximum size for image uploads in MB'
description: 'The maximum image size might be limited by your server settings.'
allowsvg:
type: checkbox
label: Allow svg
checkboxlabel: Allow the upload of svg images
checkboxlabel: 'Allow the upload of svg images'
convertwebp:
type: checkbox
label: Convert to webp
checkboxlabel: Try to convert uploaded images into the webp-format
label: 'Convert to webp'
checkboxlabel: 'Try to convert uploaded images into the webp-format for better performance.'
maxfileuploads:
type: number
label: Maximum size for file uploads in MB
description: The maximum file size might be limited by your server settings.
label: 'Maximum size for file uploads in MB'
description: 'The maximum file size might be limited by your server settings.'
fieldsetwriting:
type: fieldset
legend: Writing
fields:
editor:
type: radio
label: Standard editor mode
label: 'Standard editor mode'
css: lg:w-half
options:
'visual': 'visual editor'
'raw': 'raw editor'
formats:
type: checkboxlist
label: Format options for visual editor
label: 'Format options for visual editor'
css: lg:w-half
options:
'markdown': 'markdown'
@@ -124,12 +128,12 @@ fieldsetwriting:
'shortcode': 'shortcode'
headlineanchors:
type: checkbox
label: Headline anchors
checkboxlabel: Show anchors next to headline in frontend
label: 'Headline anchors'
checkboxlabel: 'Show anchors next to headline in frontend'
urlschemes:
type: text
label: Url schemes
description: Add more url schemes for external links e.g. like dict:// (comma separated list)
label: 'Url schemes'
description: 'Add more url schemes for external links e.g. like dict:// (comma separated list)'
maxlength: 60
fieldsetaccess:
type: fieldset
@@ -137,57 +141,83 @@ fieldsetaccess:
fields:
access:
type: checkbox
label: Website restriction
checkboxlabel: Show the website only to authenticated users and redirect all other users to the login page.
label: 'Website restriction'
checkboxlabel: 'Show the website only to authenticated users and redirect all other users to the login page.'
pageaccess:
type: checkbox
label: Page restriction
checkboxlabel: Activate individual restrictions for pages in the meta-tab of each page.
label: 'Page restriction'
checkboxlabel: 'Activate individual restrictions for pages in the meta-tab of each page.'
hrdelimiter:
type: checkbox
label: Content break
checkboxlabel: Cut restricted content after the first hr-element on a page (per default content will be cut after title).
label: 'Content break'
checkboxlabel: 'Cut restricted content after the first hr-element on a page (per default content will be cut after title).'
restrictionnotice:
type: textarea
label: Restriction notice (use markdown)
label: 'Restriction notice (use markdown)'
maxlength: 2000
wraprestrictionnotice:
type: checkbox
label: Wrap restriction notice
checkboxlabel: Wrap the restriction notice above into a notice-4 element (which can be designed as special box)
label: 'Wrap restriction notice'
checkboxlabel: 'Wrap the restriction notice above into a notice-4 element (which can be designed as special box)'
fieldsetmail:
type: fieldset
legend: Email
fields:
mailfrom:
type: text
label: 'Mail From (required)'
placeholder: sender@yourmail.org
maxlength: 60
description: 'Enter an email address that should send emails (sender).'
mailfromname:
type: text
label: 'Mail From Name (optional)'
placeholder: sender name
maxlength: 60
description: 'Optionally enter a name for the sender address. If not set, the from-address will be visible.'
replyto:
type: text
label: 'Reply To (optional)'
placeholder: noreply@yourmail.org
maxlength: 60
description: 'Optionally enter a "reply to" address for answers from the receiver. If not set, answers will go to the from-address.'
fieldsetrecover:
type: fieldset
legend: Recover
fields:
recoverpw:
type: checkbox
label: 'Recover password'
checkboxlabel: 'Activate a password recovery in the login form.'
recoversubject:
type: text
label: 'Email subject'
placeholder: 'Recover your password'
maxlength: 60
recovermessage:
type: textarea
label: 'Text before recover link in email message'
maxlength: 2000
fieldsetsecurity:
type: fieldset
legend: Security
fields:
securitylog:
type: checkbox
label: Security log
checkboxlabel: Track spam and suspicious actions in a logfile
label: 'Security log'
checkboxlabel: 'Track spam and suspicious actions in a logfile'
authcode:
type: checkbox
label: 'Authentication code (recommended)'
checkboxlabel: 'Send a 5-digit authentication code by email to confirm login.'
description: 'The authentication code will be valid for 5 minutes. Be aware that device fingerprints will be stored in the user accounts. Make sure this complies with privacy legislation in your country.'
authcaptcha:
type: radio
label: Use captcha in authentication forms
label: 'Use captcha in authentication forms'
options:
disabled: Disable
standard: Always show
aftererror: Show after first wrong input
recoverpw:
type: checkbox
label: Recover password
checkboxlabel: Activate the password recovery.
recoverfrom:
type: text
label: Sender email
placeholder: your@email.org
maxlength: 60
recoversubject:
type: text
label: Email subject
placeholder: Recover your password
maxlength: 60
recovermessage:
type: textarea
label: Text before recover link in email message
maxlength: 2000
disabled: 'Disable'
standard: 'Always show'
aftererror: 'Show after first wrong input'
fieldsetdeveloper:
type: fieldset
legend: Developer