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

v2.4.2 add loginlink feature

This commit is contained in:
trendschau
2024-04-22 17:05:08 +02:00
parent 2eb496fbf1
commit 4d6726b7be
8 changed files with 171 additions and 11 deletions

View File

@@ -1,3 +1,4 @@
127.0.0.1;2024-03-25 21:48:49;login: wrong password
127.0.0.1;2024-04-20 12:51:39;login: wrong password
127.0.0.1;2024-04-21 19:24:11;login: invalid data
127.0.0.1;2024-04-22 14:38:20;loginlink: loginlink for user member is not activated.

View File

@@ -221,8 +221,15 @@ class ControllerApiSystemUsers extends Controller
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
}
# check if loginlink is activated
$loginlink = false;
if($userdata['userrole'] == 'member' && isset($this->settings['loginlink']) && $this->settings['loginlink'])
{
$loginlink = true;
}
# we have to validate again because of additional dynamic fields
$formdefinitions = $user->getUserFields($this->c->get('acl'), $request->getAttribute('c_userrole'));
$formdefinitions = $user->getUserFields($this->c->get('acl'), $request->getAttribute('c_userrole'), NULL, $loginlink);
$validatedOutput = $validate->recursiveValidation($formdefinitions, $userdata);
if(!empty($validate->errors))
{

View File

@@ -241,6 +241,136 @@ class ControllerWebAuth extends Controller
return $response->withHeader('Location', $this->routeParser->urlFor($redirect))->withStatus(302);
}
public function loginlink(Request $request, Response $response, $args)
{
if(!isset($this->settings['loginlink']) OR !$this->settings['loginlink'])
{
if($securitylog)
{
\Typemill\Static\Helpers::addLogEntry('loginlink: not activated');
}
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
}
# optionally check trusted ips
$trustedLogin = ( isset($this->settings['trustedloginreferrer']) && !empty($this->settings['trustedloginreferrer']) ) ? explode(",", $this->settings['trustedloginreferrer']) : [];
$ipAddress = $_SERVER['REMOTE_ADDR'] ?? null;
if (
!empty($trustedLogin)
&& !in_array($ipAddress, $trustedLogin)
)
{
if($securitylog)
{
\Typemill\Static\Helpers::addLogEntry('loginlink: remote address is not a trusted ip');
}
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
}
$input = $request->getQueryParams();
$validation = new Validation();
$securitylog = $this->settings['securitylog'] ?? false;
if($validation->signin($input) !== true)
{
if($securitylog)
{
\Typemill\Static\Helpers::addLogEntry('loginlink: invalid data');
}
if($this->c->get('flash'))
{
$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);
}
$user = new User();
if(!$user->setUserWithPassword($input['username']))
{
if($securitylog)
{
\Typemill\Static\Helpers::addLogEntry('loginlink: user not found');
}
if($this->c->get('flash'))
{
$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();
if($userdata['userrole'] != 'member')
{
if($securitylog)
{
\Typemill\Static\Helpers::addLogEntry('loginlink: user has not a member role. Only members can use loginlinks.');
}
if($this->c->get('flash'))
{
$this->c->get('flash')->addMessage('error', Translations::translate('User is not a member.'));
}
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
}
if(!isset($userdata['linkaccess']) OR ($userdata['linkaccess'] !== true))
{
if($securitylog)
{
\Typemill\Static\Helpers::addLogEntry('loginlink: loginlink for user ' . $userdata['username'] . ' is not activated.');
}
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');
}
if($this->c->get('flash'))
{
$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 if user has confirmed the account
if(isset($userdata['optintoken']) && $userdata['optintoken'])
{
if($securitylog)
{
\Typemill\Static\Helpers::addLogEntry('login: user not confirmed yet.');
}
if($this->c->get('flash'))
{
$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();
$redirect = $this->getRedirectDestination($userdata['userrole']);
return $response->withHeader('Location', $this->routeParser->urlFor($redirect))->withStatus(302);
}
private function getRedirectDestination(string $userrole)
{
# decide where to redirect after login, configurable in settings -> system.yaml

View File

@@ -361,7 +361,12 @@ class ControllerWebSystem extends Controller
$userdata = $user->getUserData();
$inspector = $request->getAttribute('c_userrole');
$userfields = $user->getUserFields($this->c->get('acl'), $userdata['userrole'], $inspector);
$loginlink = false;
if($userdata['userrole'] == 'member' && isset($this->settings['loginlink']) && $this->settings['loginlink'])
{
$loginlink = true;
}
$userfields = $user->getUserFields($this->c->get('acl'), $userdata['userrole'], $inspector, $loginlink);
return $this->c->get('view')->render($response, 'system/user.twig', [
'settings' => $this->settings,

View File

@@ -169,7 +169,7 @@ class User
return false;
}
public function getUserFields($acl, $userrole, $inspectorrole = NULL)
public function getUserFields($acl, $userrole, $inspectorrole = NULL, $loginlink = NULL)
{
$storage = new StorageWrapper('\Typemill\Models\Storage');
$userfields = $storage->getYaml('systemSettings', '', 'user.yaml');
@@ -217,7 +217,12 @@ class User
$userfields['userrole'] = ['label' => Translations::translate('Role'), 'type' => 'select', 'options' => $options];
# can activate api access
$userfields['apiaccess'] = ['label' => Translations::translate('API access'), 'checkboxlabel' => Translations::translate('Activate API access for this user. Use username and password for api calls'), 'type' => 'checkbox'];
$userfields['apiaccess'] = ['label' => Translations::translate('API access'), 'checkboxlabel' => Translations::translate('Activate API access for this user. Use username and password for api calls. Whitelist calling domains in the developer settings.'), 'type' => 'checkbox'];
if($loginlink)
{
$userfields['linkaccess'] = ['label' => Translations::translate('Link access'), 'checkboxlabel' => Translations::translate('Activate link access for this user (only for member role). Use username and password for the link. Optionally whitelist IPs in the developer settings.'), 'type' => 'checkbox'];
}
}
return $userfields;

View File

@@ -19,6 +19,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['loginlink']) && $settings['loginlink'])
{
$group->get('/loginlink', ControllerWebAuth::class . ':loginlink')->setName('auth.link');
}
if(isset($settings['authcode']) && $settings['authcode'])
{
$group->post('/authcode', ControllerWebAuth::class . ':loginWithAuthcode')->setName('auth.authcode');

View File

@@ -289,4 +289,11 @@ fieldsetdeveloper:
label: "Allowed Domains for API-Access (CORS-Headers)"
placeholder: 'https://my-website-that-uses-the-api.org,https://another-website-using-the-api.org'
description: "List all domains, separated by comma, that should have access to the Typemill API. Domains will be added to the cors-header."
loginlink:
type: checkbox
label: "Login with link"
checkboxlabel: "Allow selected users to login with a login link."
description: "If activated, you can allow login-links with a checkbox in the user profile. This is only available for member-roles since members have very limited rights. Login with a link can be helpful if you link from your software to a non-public documentation. Be aware of the low protection that this kind of logins have. If you integrate such links in a SaaS-software, then you should restrict access to your ips."
trustedloginreferrer:
type: text
label: "Trusted IPs for the login-link-referrer (comma separated)"

View File

@@ -21,6 +21,11 @@ userrole:
label: 'Role'
type: 'text'
readonly: true
darkmode:
name: darkmode
label: 'Darkmode'
checkboxlabel: 'Activate the darkmode for me'
type: 'checkbox'
password:
name: password
label: 'Actual Password'
@@ -31,9 +36,4 @@ newpassword:
label: 'New Password'
type: 'password'
autocomplete: 'new-password'
generator: true
darkmode:
name: darkmode
label: 'Darkmode'
checkboxlabel: 'Activate the darkmode for me'
type: 'checkbox'
generator: true