Initial commit

This commit is contained in:
Abel Hoogeveen 2023-02-06 15:44:28 +01:00
commit 04c84ca83b
48 changed files with 4917 additions and 0 deletions

4
.gitattributes vendored Normal file
View File

@ -0,0 +1,4 @@
.gitattributes export-ignore
.gitignore export-ignore
.drone.yml export-ignore
test/ export-ignore

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
composer.lock
composer.phar
.idea/
build/
test/temp/
vendor/
test/.phpunit.result.cache

21
LICENSE Executable file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (C) 2013 - 2023 i15
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

33
composer.json Executable file
View File

@ -0,0 +1,33 @@
{
"name": "fuzeworks/authentication",
"license": ["MIT"],
"authors": [
{
"name": "Abel Hoogeveen",
"email": "abel@i15.nl"
}
],
"require": {
"php": ">=8.1.0",
"fuzeworks/core": "~1.3.0",
"fuzeworks/mvcr": "~1.3.0",
"fuzeworks/database": "~1.3.0",
"fuzeworks/objectstorage": "~1.3.0",
"fuzeworks/layout": "~1.3.0",
"fuzeworks/forms": "~1.3.0",
"fuzeworks/mailer-wrapper": "~1.3.0"
},
"suggest": {
"fuzeworks/webcomponent": "Provides a web-based frontend for authentication"
},
"require-dev": {
"phpunit/phpunit": "^9",
"fuzeworks/webcomponent": "~1.3.0",
"fuzeworks/tracycomponent": "~1.3.0"
},
"autoload": {
"psr-4": {
"FuzeWorks\\Authentication\\": "src/FuzeWorks/Authentication/"
}
}
}

58
config.authentication.php Executable file
View File

@ -0,0 +1,58 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
return [
// Whether the plugin is enabled
'auth_enabled' => true,
'auth_url' => "auth",
// Whether registration is enabled on this website
'register_enabled' => true,
// Whether the user should be able to manually change their passwords
'forgot_password_enabled' => true,
// The database group to use for authentication
'database_group' => 'default',
// Password settings
'password_min_length' => 8,
'password_min_score' => 3,
'password_algorithm' => PASSWORD_DEFAULT,
'password_options' => [],
// Email settings
'verifyEmailWithin' => 3600*24*3
];

View File

@ -0,0 +1,485 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace Application\Controller;
use FuzeWorks\Authentication\Events\LoginEvent;
use FuzeWorks\Authentication\Events\RegisterEvent;
use FuzeWorks\Authentication\Exceptions\AuthenticationException;
use FuzeWorks\Authentication\Exceptions\InputException;
use FuzeWorks\Authentication\Exceptions\LoginErrorException;
use FuzeWorks\Authentication\Exceptions\LoginWarningException;
use FuzeWorks\Authentication\Exceptions\RegisterErrorException;
use FuzeWorks\Authentication\Exceptions\RegisterWarningException;
use FuzeWorks\Authentication\Model\Session;
use FuzeWorks\Authentication\Model\User;
use FuzeWorks\ConfigORM\ConfigORM;
use FuzeWorks\Controller;
use FuzeWorks\Authentication\AuthenticationPlugin;
use FuzeWorks\Events;
use FuzeWorks\Exception\FactoryException;
use FuzeWorks\Exception\LayoutException;
use FuzeWorks\Factory;
use FuzeWorks\Forms\Form;
use FuzeWorks\Layout;
use FuzeWorks\Logger;
use FuzeWorks\Mailer\PHPMailerWrapper;
use FuzeWorks\ObjectStorage\ObjectStorageComponent;
use PHPMailer\PHPMailer\Exception;
use Psr\SimpleCache\CacheInterface;
class AuthenticationController extends Controller
{
protected AuthenticationPlugin $plugin;
public Session $session;
public ConfigORM $authConfig;
public function __construct()
{
parent::__construct();
$this->plugin = $this->plugins->get('auth');
$this->session = $this->plugin->sessions->start();
$this->authConfig = $this->plugin->config;
}
/**
* Attempt a login using a designated identifier and password.
*
* Sessions are automatically extended as they approach their end. Sessions with remember = false will last an hour.
* Sessions with remember = true will last 3 months.
*
* Context data will be saved into the database for security purposes.
*
* Login may be prevented by the following causes:
* - Username and password mismatch, or password is not set.
* - The user is already currently logged in the current session.
* - The user doesn't exist.
* - User is set to inactive.
* - Email verification threshold has expired and the user must verify before being allowed to log in.
* - The event system cancels login for other reasons.
*
* @param string $identifier
* @param string $password
* @param bool $remember
* @param array $context
* @return Session
* @throws LoginErrorException
* @throws LoginWarningException
*/
public function login(string $identifier, string $password, bool $remember = false, array $context = []): Session
{
/** @var LoginEvent $event */
$event = Events::fireEvent(new LoginEvent($identifier, $password, $remember, $context));
if ($event->isCancelled())
throw new LoginErrorException("Login cancelled by system.");
// Fetch the user
$user = $this->plugin->users->getUserByEmail($event->identifier);
foreach ($event->getIdentifierFields() as $field)
{
if (empty($user))
$user = $this->plugin->users->getUsersByProperty($field, $event->identifier);
}
// If user not found, notify
if (empty($user))
throw new LoginWarningException("The provided username and/or password is invalid.");
// Verify that the current session is not the same as the supposed login session
if ($this->session->user->id === $user->id)
throw new LoginWarningException("User is already logged in.");
// Check if the password is correct
if (!password_verify($event->password, $user->password))
throw new LoginWarningException("The provided username and/or password is invalid.");
// Check if the password needs to be updated
if (password_needs_rehash($user->password,
$this->plugin->config->get("password_algorithm"),
$this->plugin->config->get("password_options")))
{
$this->plugin->users->changePassword($user, $event->password);
}
// Check if the user is still active
if (!$user->active)
throw new LoginErrorException("User is inactive. Login blocked.");
// Check if email has been verified on time
if (!is_null($user->emailVerifyToken) && date('U') > $user->emailVerifyExpiry)
{
$url = $this->plugin->getAuthenticationURL() . "/resend_verify_email?t=" . base64_encode($user->primaryEmail);
throw new LoginWarningException("User must verify email before logging in. Click <a href='$url'>here</a> to resend the verification email.");
}
// Create a session, log and return it
$this->session = $this->plugin->sessions->createSession($user, $event->context, $event->remember);
Logger::log("Logged in user '" . $user->primaryEmail . "'");
return $this->session;
}
/**
* Log a user out of the current session.
*
* Will set the session active to false and remove the session cookie.
*
* @return bool
*/
public function logout(): bool
{
if ($this->session->user->id == "0")
return false;
return $this->plugin->sessions->endSession($this->session);
}
/**
* Performs a registration of a new user using the provided data.
*
* The only absolutely required parameter is email. No user can exist without a valid email address.
*
* If password is null (for instance by creating the user from a panel instead of registration), the user shall be asked to set a password after verifying their email.
*
* Parameters should be an empty array, unless the developer wants to explicitly set internal values like emailExpiryTime.
*
* Properties can be any extra key => value data the developer wants to store about their users, such as usernames or social data.
*
* Use $bypassRestrictions in case registration is disabled by config, but a new user must be added anyway.
*
* Registration can be denied for the following reasons:
* - Registration is disabled by config and $bypassRestrictions is not set to true.
* - User already exists.
* - The event system cancels registration of another reason.
*
* @param string $email
* @param string|null $password
* @param array $parameters
* @param array $properties
* @param bool $bypassRestrictions
* @return bool
* @throws RegisterErrorException
* @throws RegisterWarningException
*/
public function register(string $email, ?string $password, array $parameters, array $properties, bool $bypassRestrictions = false): bool
{
// First check if registration is enabled
if (!$this->authConfig->get("register_enabled") && !$bypassRestrictions)
throw new RegisterErrorException("Registration is disabled.");
/** @var RegisterEvent $event */
$event = Events::fireEvent(new RegisterEvent($email, $password, $parameters, $properties));
if ($event->isCancelled())
{
$error = is_null($event->getError()) ? "Register cancelled by system." : $event->getError();
throw new RegisterErrorException($error);
}
// Create the user
try {
if (!$this->plugin->users->addUser($event->email, $event->password, true, $event->properties))
throw new RegisterErrorException("Could not create user. System error.");
// Fetch the user
$user = $this->plugin->users->getUserByEmail($event->email);
$this->sendVerifyEmail($user);
} catch (InputException|AuthenticationException $e) {
throw new RegisterErrorException($e->getMessage());
} catch (Exception $e) {
throw new RegisterErrorException("Could not add user. Failed to send mail to user: " . $e->getMessage());
} catch (FactoryException|LayoutException $e) {
throw new RegisterErrorException("Could not add user. System failure.");
}
return true;
}
/**
* Sends a new email verification message to the user, in case the existing message is lost or expired.
*
* @param string $email
* @return bool
* @throws LoginErrorException
* @throws RegisterErrorException
*/
public function resendVerifyEmail(string $email): bool
{
$user = $this->plugin->users->getUserByEmail($email);
if (is_null($user))
throw new LoginErrorException("User not found.");
try {
$this->sendVerifyEmail($user);
} catch (FactoryException|LayoutException $e) {
throw new LoginErrorException("Could not resend verify email. System failure.");
} catch (Exception $e) {
throw new RegisterErrorException("Could not resend verify email. Failed to send mail to user: " . $e->getMessage());
}
return true;
}
/**
* Sends a email verification message to the user.
*
* @param User $user
* @return void
*/
private function sendVerifyEmail(User $user): void
{
// Load template engine
/** @var Layout $layout */
$layout = Factory::getInstance("layouts");
$layout->assign("user", $user);
$layout->assign("verifyURL", $this->plugin->getAuthenticationURL() . "/verify");
$html = $layout->get("mail/register");
$alt = $layout->get("mail/register_alt");
// Prepare mailer
/** @var PHPMailerWrapper $mailer */
$mailer = $this->libraries->get("mailer");
$mailer->addAddress($user->primaryEmail);
$serverName = $this->config->getConfig("web")->get("serverName");
$mailer->Subject = "Welcome to $serverName !";
$mailer->isHTML();
$mailer->Body = $html;
$mailer->AltBody = $alt;
// And send mail
$mailer->send();
}
/**
* Verifies a user by their email token.
*
* @param string $token
* @return User|null
*/
public function verifyEmail(string $token): ?User
{
// Fetch user
$users = $this->plugin->users->getUsersByVariable("emailVerifyToken", $token);
if (count($users) === 1)
{
// Select user
/** @var User $user */
$user = $users[0];
$user->emailVerifyToken = null;
$user->emailVerifyExpiry = null;
// Update the user
$this->plugin->users->updateUser($user);
// And return true
return $user;
}
return null;
}
/**
* Sends a forgot password email to the user, based on the email address of the user.
*
* Will be denied if:
* - Password resets are disabled by config.
* - The user doesn't exist.
* - The user is marked as inactive.
*
* @param string $email
* @return bool
* @throws FactoryException
* @throws LoginErrorException
* @throws LoginWarningException
*/
public function forgotPassword(string $email): bool
{
// Check if forgot password is enabled
if (!$this->authConfig->get("forgot_password_enabled"))
throw new LoginErrorException("Forgot password is disabled.");
// Fetch the user
$user = $this->plugin->users->getUserByEmail($email);
// If user not found, notify
if (empty($user))
throw new LoginWarningException("The provided email is unknown.");
// Check if the user is still active
if (!$user->active)
throw new LoginErrorException("The provided email is unknown");
// Generate reset token
$token = bin2hex(random_bytes(32));
// Save the token and user ID to ObjectStorage
/** @var CacheInterface $cache */
$cache = Factory::getInstance("storage")->getCache();
$cache->set("forgot_password_" . $token, $user->id, 3600);
// And send mail
try {
// Data is verified. Now send the email.
/** @var Layout $layout */
$layout = Factory::getInstance("layouts");
$layout->assign("resetURL", $this->plugin->getAuthenticationURL() . "/reset_password");
$layout->assign("token", $token);
$html = $layout->get("mail/forgot_password");
$alt = $layout->get("mail/forgot_password_alt");
// Prepare mailer
/** @var PHPMailerWrapper $mailer */
$mailer = $this->libraries->get("mailer");
$mailer->addAddress($email);
$mailer->Subject = "Password reset";
$mailer->isHTML();
$mailer->Body = $html;
$mailer->AltBody = $alt;
$mailer->send();
} catch (Exception $e) {
throw new LoginErrorException("Could not send mail to user: " . $e->getMessage());
} catch (FactoryException|LayoutException $e) {
throw new LoginErrorException("Could not send mail to user. System failure.");
}
return true;
}
/**
* Method used by AdminView to generate a token to reset password with, after verifying email.
*
* @internal
* @param User $user
* @return string
*/
public function createResetToken(User $user): string
{
// Generate reset token
$token = bin2hex(random_bytes(32));
// Save the token and user ID to ObjectStorage
/** @var CacheInterface $cache */
$cache = Factory::getInstance("storage")->getCache();
$cache->set("forgot_password_" . $token, $user->id, 3600);
return $token;
}
/**
* Reset the password of the user with the selected token
*
* @param string $token
* @param string $password
* @return bool
* @throws LoginErrorException
*/
public function resetPassword(string $token, string $password): bool
{
// Check token in cache
/** @var CacheInterface $cache */
$cache = Factory::getInstance("storage")->getCache();
if (!$cache->has("forgot_password_" . $token))
throw new LoginErrorException("User was not found.");
// Fetch user
$user = $this->plugin->users->getUserByUid($cache->get("forgot_password_" . $token));
// Modify the user
try {
if ($this->plugin->users->changePassword($user, $password))
$cache->delete("forgot_password_" . $token);
return true;
} catch (InputException $e) {
throw new LoginErrorException($e->getMessage());
}
}
/**
* Returns an array of detailed user information.
*
* Returns ['user' => User, 'sessions' => Session[]] , or null if not found.
*
* @param string $userId
* @return array|null
*/
public function getDetailedUserInformation(string $userId): ?array
{
$user = $this->plugin->users->getUserByUid($userId);
if (is_null($user))
return null;
$sessions = $this->plugin->sessions->getSessions(['user' => $userId]);
return ['user' => $user, 'sessions' => $sessions];
}
/**
* Gives a list of users that match the filter.
*
* @param array $filter
* @param int $index
* @param int $limit
* @return array
*/
public function listUsers(array $filter = [], int $index = 0, int $limit = 25): array
{
return $this->plugin->users->getUsers($index, $limit);
}
/**
* Condition callable for register form, to check if password 1 and 2 are equivalent.
*
* @internal
* @param Form $form
* @return bool
*/
public static function registerFormCondition(Form $form): bool
{
// Verify password1 and password2 equivalency
$pass1 = $form->getField("password1");
$pass2 = $form->getField("password2");
if ($pass1->getValue() !== $pass2->getValue())
{
$pass1->invalidate();
$pass2->invalidate();
$pass1->addError("Passwords do not match");
$pass2->addError("Passwords do not match");
return false;
}
return true;
}
}

View File

@ -0,0 +1,5 @@
<div class="container-fluid">
<div class="row">
{$form|noescape}
</div>
</div>

View File

@ -0,0 +1,65 @@
{varType FuzeWorks\Authentication\Model\User[] $users}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">List of registered users on this system</h3>
<a href="/{$adminKey}/authentication/create" class="btn btn-sm btn-primary float-right">Create User</a>
</div>
<!-- /.card-header -->
<div class="card-body">
<table id="example2" class="table table-bordered table-hover">
<thead>
<tr>
<th>Email</th>
<th>Permissions</th>
<th>Email verified</th>
<th>2FA</th>
<th>Active</th>
</tr>
</thead>
<tbody>
{foreach $users as $user}
<tr>
<td><a href="/{$adminKey}/authentication/view/{$user->id}">{$user->primaryEmail}</a></td>
<td>{$user->permissions|implode:", "}</td>
<td>{is_null($user->emailVerifyToken) ? "Yes" : "No"}</td>
<td>{is_null($user->mfaSecret) ? "No" : "Yes"}</td>
<td>{$user->active ? "Yes" : "No"}</td>
</tr>
{/foreach}
</tbody>
<tfoot>
<tr>
<th>Email</th>
<th>Permissions</th>
<th>Email verified</th>
<th>2FA</th>
<th>Active</th>
</tr>
</tfoot>
</table>
</div>
<!-- /.card-body -->
</div>
<!-- /.card -->
</div>
<!-- /.col -->
</div>
<!-- /.row -->
</div>
<script>
$(function () {
$('#example2').DataTable({
"paging": true,
"lengthChange": false,
"searching": false,
"ordering": true,
"info": true,
"autoWidth": false,
"responsive": true,
});
});
</script>

View File

@ -0,0 +1,26 @@
<?php
/** @var \FuzeWorks\Authentication\Model\Session $session */
?>
<h1 title="Authentication">Session info:</h1>
<div class="tracy-inner">
<table>
<?php
if (!is_null($session->user->id)) { ?>
<tr class='fuzeworks-authTable'>
<th>Key</th>
<th>Value</th>
</tr>
<tr><td>UserID</td><td><?= $session->user->id ?></td></tr>
<tr><td>Email</td><td><?= htmlspecialchars($session->user->primaryEmail, ENT_QUOTES, 'UTF-8') ?></td></tr>
<tr><td>SessionKey</td><td><?= htmlspecialchars($session->sessionKey, ENT_QUOTES, 'UTF-8') ?></td></tr>
<tr><td>Persistent</td><td><?= ($session->persistent ? 'true' : 'false') ?></td></tr>
<tr><td>Expire</td><td><?= date('d M Y H:i', $session->expiryDate) ?></td></tr>
<tr><td>Update</td><td><?= date('d M Y H:i', $session->expiryThreshold) ?></td></tr>
<tr><td>Permissions</td><td><?= implode(', ', $session->user->permissions) ?></td></tr>
</table>
<?php
}
?>
</div>

View File

@ -0,0 +1,13 @@
<?php
/** @var \FuzeWorks\Authentication\Model\Session $session */
$color = $session->user->id !== "0" ? '#3ba9e6' : '#666666';
$color = $session->user->hasPermission("SUPER") ? "#aa0000" : $color;
$user = $session->user->id !== "0" ? $session->user->primaryEmail : 'Guest';
?>
<span title="Auth">
<svg viewBox="0 0 1000 1000">
<path d="M500,10C229.4,10,10,229.4,10,500c0,270.7,219.4,490,490,490c270.7,0,490-219.4,490-490C990,229.4,770.7,10,500,10z M381.2,220.6c31.7-31.7,73.8-49.1,118.9-49.1c44.9,0,87.3,17.4,118.7,49.1c32,31.8,49.5,73.9,49.5,118.9c0,44.9-17.5,86.9-49.5,118.7c-31.5,31.7-73.8,49.3-118.7,49.3c-45,0-87.1-17.5-118.9-49.3c-31.7-31.7-49.3-73.9-49.3-118.7C331.9,294.5,349.5,252.4,381.2,220.6z M500,895c-125.9,0-239.7-52-320.8-135.8c36.9-105.4,135.6-185.3,245.5-185.3h150.5c109.7,0,208.6,79.9,245.6,185.2C739.6,843,625.9,895,500,895z" fill-opacity=".9" fill="<?= $color ?>"/>
</svg>
<span class="tracy-label"><?= $user ?></span>
</span>

View File

@ -0,0 +1,215 @@
{varType string $serverName}
{varType string $resetURL}
{varType string $token}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<!-- Compiled with Bootstrap Email version: 1.3.1 --><meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="x-apple-disable-message-reformatting">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style type="text/css" n:syntax="off">
body,table,td{font-family:Helvetica,Arial,sans-serif !important}.ExternalClass{width:100%}.ExternalClass,.ExternalClass p,.ExternalClass span,.ExternalClass font,.ExternalClass td,.ExternalClass div{line-height:150%}a{text-decoration:none}*{color:inherit}a[x-apple-data-detectors],u+#body a,#MessageViewBody a{color:inherit;text-decoration:none;font-size:inherit;font-family:inherit;font-weight:inherit;line-height:inherit}img{-ms-interpolation-mode:bicubic}table:not([class^=s-]){font-family:Helvetica,Arial,sans-serif;mso-table-lspace:0pt;mso-table-rspace:0pt;border-spacing:0px;border-collapse:collapse}table:not([class^=s-]) td{border-spacing:0px;border-collapse:collapse}@media screen and (max-width: 600px){.w-full,.w-full>tbody>tr>td{width:100% !important}*[class*=s-lg-]>tbody>tr>td{font-size:0 !important;line-height:0 !important;height:0 !important}.s-2>tbody>tr>td{font-size:8px !important;line-height:8px !important;height:8px !important}.s-3>tbody>tr>td{font-size:12px !important;line-height:12px !important;height:12px !important}.s-5>tbody>tr>td{font-size:20px !important;line-height:20px !important;height:20px !important}.s-10>tbody>tr>td{font-size:40px !important;line-height:40px !important;height:40px !important}}
</style>
</head>
<body class="bg-light" style="outline: 0; width: 100%; min-width: 100%; height: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: Helvetica, Arial, sans-serif; line-height: 24px; font-weight: normal; font-size: 16px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; color: #000000; margin: 0; padding: 0; border-width: 0;" bgcolor="#f7fafc">
<table class="bg-light body" valign="top" role="presentation" border="0" cellpadding="0" cellspacing="0" style="outline: 0; width: 100%; min-width: 100%; height: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: Helvetica, Arial, sans-serif; line-height: 24px; font-weight: normal; font-size: 16px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; color: #000000; margin: 0; padding: 0; border-width: 0;" bgcolor="#f7fafc">
<tbody>
<tr>
<td valign="top" style="line-height: 24px; font-size: 16px; margin: 0;" align="left" bgcolor="#f7fafc">
<table class="container" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">
<tbody>
<tr>
<td align="center" style="line-height: 24px; font-size: 16px; margin: 0; padding: 0 16px;">
<!--[if (gte mso 9)|(IE)]>
<table align="center" role="presentation">
<tbody>
<tr>
<td width="600">
<![endif]-->
<table align="center" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%; max-width: 600px; margin: 0 auto;">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
<table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
&#160;
</td>
</tr>
</tbody>
</table>
<table class="card" role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-radius: 6px; border-collapse: separate !important; width: 100%; overflow: hidden; border: 1px solid #e2e8f0;" bgcolor="#ffffff">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; width: 100%; margin: 0;" align="left" bgcolor="#ffffff">
<table class="card-body" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; width: 100%; margin: 0; padding: 20px;" align="left">
<h1 class="h3" style="padding-top: 0; padding-bottom: 0; font-weight: 500; vertical-align: baseline; font-size: 28px; line-height: 33.6px; margin: 0;" align="left">Password reset request for {$serverName}</h1>
<table class="s-2 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 8px; font-size: 8px; width: 100%; height: 8px; margin: 0;" align="left" width="100%" height="8">
&#160;
</td>
</tr>
</tbody>
</table>
<table class="s-5 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 20px; font-size: 20px; width: 100%; height: 20px; margin: 0;" align="left" width="100%" height="20">
&#160;
</td>
</tr>
</tbody>
</table>
<table class="hr" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; border-top-width: 1px; border-top-color: #e2e8f0; border-top-style: solid; height: 1px; width: 100%; margin: 0;" align="left">
</td>
</tr>
</tbody>
</table>
<table class="s-5 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 20px; font-size: 20px; width: 100%; height: 20px; margin: 0;" align="left" width="100%" height="20">
&#160;
</td>
</tr>
</tbody>
</table>
<div class="space-y-3">
<p class="text-gray-700" style="line-height: 24px; font-size: 16px; color: #4a5568; width: 100%; margin: 0;" align="left">If you've reset your password or wish to reset it, use the link below to get started.</p>
<table class="s-3 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 12px; font-size: 12px; width: 100%; height: 12px; margin: 0;" align="left" width="100%" height="12">
&#160;
</td>
</tr>
</tbody>
</table>
<p class="text-gray-700" style="line-height: 24px; font-size: 16px; color: #4a5568; width: 100%; margin: 0;" align="left">
If you did not request a password reset, please ignore this email. Only a person with access to your email account can reset your password.
</p>
<table class="s-3 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 12px; font-size: 12px; width: 100%; height: 12px; margin: 0;" align="left" width="100%" height="12">
&#160;
</td>
</tr>
</tbody>
</table>
<p class="text-gray-700" style="line-height: 24px; font-size: 16px; color: #4a5568; width: 100%; margin: 0;" align="left">
If you have any questions, feel free to contact us using the contact form on the website.
</p>
<table class="s-3 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 12px; font-size: 12px; width: 100%; height: 12px; margin: 0;" align="left" width="100%" height="12">
&#160;
</td>
</tr>
<tr>
<td style="line-height: 12px; font-size: 12px; width: 100%; height: 12px; margin: 0;" align="left" width="100%" height="12">
&#160;
</td>
</tr>
</tbody>
</table>
<p class="text-gray-700" style="line-height: 24px; font-size: 16px; color: #4a5568; width: 100%; margin: 0;" align="left">
With kind regards,
</p>
<table class="s-3 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 12px; font-size: 12px; width: 100%; height: 12px; margin: 0;" align="left" width="100%" height="12">
&#160;
</td>
</tr>
</tbody>
</table>
<p class="text-gray-700" style="line-height: 24px; font-size: 16px; color: #4a5568; width: 100%; margin: 0;" align="left">
<b>The {$serverName} team.</b>
</p>
</div>
<table class="s-5 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 20px; font-size: 20px; width: 100%; height: 20px; margin: 0;" align="left" width="100%" height="20">
&#160;
</td>
</tr>
</tbody>
</table>
<table class="hr" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; border-top-width: 1px; border-top-color: #e2e8f0; border-top-style: solid; height: 1px; width: 100%; margin: 0;" align="left">
</td>
</tr>
</tbody>
</table>
<table class="s-5 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 20px; font-size: 20px; width: 100%; height: 20px; margin: 0;" align="left" width="100%" height="20">
&#160;
</td>
</tr>
</tbody>
</table>
<table class="btn btn-primary" role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-radius: 6px; border-collapse: separate !important;">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; border-radius: 6px; margin: 0;" align="center" bgcolor="#0d6efd">
<a href="{$resetURL}?t={$token}" target="_blank" style="color: #ffffff; font-size: 16px; font-family: Helvetica, Arial, sans-serif; text-decoration: none; border-radius: 6px; line-height: 20px; display: block; font-weight: normal; white-space: nowrap; background-color: #0d6efd; padding: 8px 12px; border: 1px solid #0d6efd;">Click here to reset your password!</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
&#160;
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</tbody>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -0,0 +1,13 @@
{varType string $serverName}
{varType string $resetURL}
{varType string $token}
Password reset request for {$serverName}!
If you've reset your password or wish to reset it, use the link below to get started.
{$resetURL}?t={$token} Click here to reset your password.
If you did not request a password reset, please ignore this email. Only a person with access to your email account can reset your password.
With kind regards,
The {$serverName} team.

View File

@ -0,0 +1,214 @@
{varType FuzeWorks\Authentication\Model\User $user}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<!-- Compiled with Bootstrap Email version: 1.3.1 --><meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="x-apple-disable-message-reformatting">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style type="text/css" n:syntax="off">
body,table,td{font-family:Helvetica,Arial,sans-serif !important}.ExternalClass{width:100%}.ExternalClass,.ExternalClass p,.ExternalClass span,.ExternalClass font,.ExternalClass td,.ExternalClass div{line-height:150%}a{text-decoration:none}*{color:inherit}a[x-apple-data-detectors],u+#body a,#MessageViewBody a{color:inherit;text-decoration:none;font-size:inherit;font-family:inherit;font-weight:inherit;line-height:inherit}img{-ms-interpolation-mode:bicubic}table:not([class^=s-]){font-family:Helvetica,Arial,sans-serif;mso-table-lspace:0pt;mso-table-rspace:0pt;border-spacing:0px;border-collapse:collapse}table:not([class^=s-]) td{border-spacing:0px;border-collapse:collapse}@media screen and (max-width: 600px){.w-full,.w-full>tbody>tr>td{width:100% !important}*[class*=s-lg-]>tbody>tr>td{font-size:0 !important;line-height:0 !important;height:0 !important}.s-2>tbody>tr>td{font-size:8px !important;line-height:8px !important;height:8px !important}.s-3>tbody>tr>td{font-size:12px !important;line-height:12px !important;height:12px !important}.s-5>tbody>tr>td{font-size:20px !important;line-height:20px !important;height:20px !important}.s-10>tbody>tr>td{font-size:40px !important;line-height:40px !important;height:40px !important}}
</style>
</head>
<body class="bg-light" style="outline: 0; width: 100%; min-width: 100%; height: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: Helvetica, Arial, sans-serif; line-height: 24px; font-weight: normal; font-size: 16px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; color: #000000; margin: 0; padding: 0; border-width: 0;" bgcolor="#f7fafc">
<table class="bg-light body" valign="top" role="presentation" border="0" cellpadding="0" cellspacing="0" style="outline: 0; width: 100%; min-width: 100%; height: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: Helvetica, Arial, sans-serif; line-height: 24px; font-weight: normal; font-size: 16px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; color: #000000; margin: 0; padding: 0; border-width: 0;" bgcolor="#f7fafc">
<tbody>
<tr>
<td valign="top" style="line-height: 24px; font-size: 16px; margin: 0;" align="left" bgcolor="#f7fafc">
<table class="container" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">
<tbody>
<tr>
<td align="center" style="line-height: 24px; font-size: 16px; margin: 0; padding: 0 16px;">
<!--[if (gte mso 9)|(IE)]>
<table align="center" role="presentation">
<tbody>
<tr>
<td width="600">
<![endif]-->
<table align="center" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%; max-width: 600px; margin: 0 auto;">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
<table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
&#160;
</td>
</tr>
</tbody>
</table>
<table class="card" role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-radius: 6px; border-collapse: separate !important; width: 100%; overflow: hidden; border: 1px solid #e2e8f0;" bgcolor="#ffffff">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; width: 100%; margin: 0;" align="left" bgcolor="#ffffff">
<table class="card-body" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; width: 100%; margin: 0; padding: 20px;" align="left">
<h1 class="h3" style="padding-top: 0; padding-bottom: 0; font-weight: 500; vertical-align: baseline; font-size: 28px; line-height: 33.6px; margin: 0;" align="left">Welcome to {$serverName}!</h1>
<table class="s-2 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 8px; font-size: 8px; width: 100%; height: 8px; margin: 0;" align="left" width="100%" height="8">
&#160;
</td>
</tr>
</tbody>
</table>
<h5 class="text-teal-700" style="color: #13795b; padding-top: 0; padding-bottom: 0; font-weight: 500; vertical-align: baseline; font-size: 20px; line-height: 24px; margin: 0;" align="left">Just one more step needed.</h5>
<table class="s-5 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 20px; font-size: 20px; width: 100%; height: 20px; margin: 0;" align="left" width="100%" height="20">
&#160;
</td>
</tr>
</tbody>
</table>
<table class="hr" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; border-top-width: 1px; border-top-color: #e2e8f0; border-top-style: solid; height: 1px; width: 100%; margin: 0;" align="left">
</td>
</tr>
</tbody>
</table>
<table class="s-5 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 20px; font-size: 20px; width: 100%; height: 20px; margin: 0;" align="left" width="100%" height="20">
&#160;
</td>
</tr>
</tbody>
</table>
<div class="space-y-3">
<p class="text-gray-700" style="line-height: 24px; font-size: 16px; color: #4a5568; width: 100%; margin: 0;" align="left">You registered an account on {$serverName}.</p>
<table class="s-3 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 12px; font-size: 12px; width: 100%; height: 12px; margin: 0;" align="left" width="100%" height="12">
&#160;
</td>
</tr>
</tbody>
</table>
<p class="text-gray-700" style="line-height: 24px; font-size: 16px; color: #4a5568; width: 100%; margin: 0;" align="left">
Before being able to use your account, you need to verify that this is your email address by clicking the button at the bottom.
</p>
<table class="s-3 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 12px; font-size: 12px; width: 100%; height: 12px; margin: 0;" align="left" width="100%" height="12">
&#160;
</td>
</tr>
</tbody>
</table>
<p class="text-gray-700" style="line-height: 24px; font-size: 16px; color: #4a5568; width: 100%; margin: 0;" align="left">
If you have any questions, feel free to contact us using the contact form on the website.
</p>
<table class="s-3 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 12px; font-size: 12px; width: 100%; height: 12px; margin: 0;" align="left" width="100%" height="12">
&#160;
</td>
</tr>
<tr>
<td style="line-height: 12px; font-size: 12px; width: 100%; height: 12px; margin: 0;" align="left" width="100%" height="12">
&#160;
</td>
</tr>
</tbody>
</table>
<p class="text-gray-700" style="line-height: 24px; font-size: 16px; color: #4a5568; width: 100%; margin: 0;" align="left">
With kind regards,
</p>
<table class="s-3 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 12px; font-size: 12px; width: 100%; height: 12px; margin: 0;" align="left" width="100%" height="12">
&#160;
</td>
</tr>
</tbody>
</table>
<p class="text-gray-700" style="line-height: 24px; font-size: 16px; color: #4a5568; width: 100%; margin: 0;" align="left">
<b>The {$serverName} team.</b>
</p>
</div>
<table class="s-5 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 20px; font-size: 20px; width: 100%; height: 20px; margin: 0;" align="left" width="100%" height="20">
&#160;
</td>
</tr>
</tbody>
</table>
<table class="hr" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; border-top-width: 1px; border-top-color: #e2e8f0; border-top-style: solid; height: 1px; width: 100%; margin: 0;" align="left">
</td>
</tr>
</tbody>
</table>
<table class="s-5 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 20px; font-size: 20px; width: 100%; height: 20px; margin: 0;" align="left" width="100%" height="20">
&#160;
</td>
</tr>
</tbody>
</table>
<table class="btn btn-primary" role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-radius: 6px; border-collapse: separate !important;">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; border-radius: 6px; margin: 0;" align="center" bgcolor="#0d6efd">
<a href="{$verifyURL}?t={$user->emailVerifyToken}" target="_blank" style="color: #ffffff; font-size: 16px; font-family: Helvetica, Arial, sans-serif; text-decoration: none; border-radius: 6px; line-height: 20px; display: block; font-weight: normal; white-space: nowrap; background-color: #0d6efd; padding: 8px 12px; border: 1px solid #0d6efd;">Click here to verify email!</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
&#160;
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</tbody>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -0,0 +1,11 @@
{varType FuzeWorks\Authentication\Model\User $user}
Welcome to {$serverName}!
You registered an account on {$serverName}. Before being able to use your account, you need to verify that this is your email address by clicking the link below.
If you have any questions, feel free to contact us using the contact form on the website.
With kind regards,
The {$serverName} team.
{$verifyURL}?t={$user->emailVerifyToken} Click here to verify email!

View File

@ -0,0 +1,34 @@
{varType bool $register_enabled}
{varType bool $forgot_password_enabled}
{varType string $auth_url}
{varType FuzeWorks\Forms\Form $form}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{$serverName} > {$form->getLabel()}</title>
<!-- Bootstrap core CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
<!-- Custom styles for this template -->
<link href="signin.css" rel="stylesheet">
</head>
<body class="text-center">
<h3>{$form->getLabel()}</h3>
{$form|noescape}
<br/>
<div class="btn-group" role="group">
{if $form->getName() == 'AuthLoginForm'}
{if $register_enabled}
<br/><a class="btn btn-outline-primary" href="{$auth_url}/register" role="button">Sign up</a>
{/if}
{elseif $form->getName() == 'AuthRegisterForm' OR $form->getName() == "AuthForgotPasswordForm"}
<br/><a class="btn btn-outline-primary" href="{$auth_url}/login" role="button">Sign in</a>
{/if}
</div>
<p class="mt-5 mb-3 text-muted">&copy; 2013 - {date('Y')}</p>
</body>
</html>

View File

@ -0,0 +1,184 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication;
use Application\Controller\AuthenticationController;
use FuzeWorks\Authentication\Model\Sessions;
use FuzeWorks\Authentication\Model\Users;
use FuzeWorks\Config;
use FuzeWorks\ConfigORM\ConfigORM;
use FuzeWorks\Database;
use FuzeWorks\DatabaseEngine\MongoEngine;
use FuzeWorks\DatabaseEngine\PDOEngine;
use FuzeWorks\Factory;
use FuzeWorks\iPluginHeader;
use FuzeWorks\Priority;
use FuzeWorks\Router;
class AuthenticationPlugin implements iPluginHeader
{
public Users $users;
public Sessions $sessions;
public ConfigORM $config;
protected string $authURL;
/**
* Starts the plugin and integrates it into the other systems of FuzeWorks.
*
* Particularly, AuthenticationPlugin first loads the MVC-components, and adds its internal directories to them.
* It then registers the /auth URL with Router and loads all the sub-components such as Users management, Sessions management and more.
*/
public function init()
{
// Load the basic requirements for every request
/** @var Config $config */
$config = Factory::getInstance('config');
$views = Factory::getInstance('views');
$controllers = Factory::getInstance('controllers');
$layout = Factory::getInstance("layouts");
// Determine path and add to components
$pluginPath = dirname(__DIR__, 3);
$config->addComponentPath($pluginPath, Priority::LOWEST);
$views->addComponentPath($pluginPath . DS . "view", Priority::LOWEST);
$controllers->addComponentPath($pluginPath . DS . "controller", Priority::LOWEST);
$layout->addComponentPath($pluginPath . DS . "layout", Priority::LOWEST);
// Open the configuration
$this->config = $config->getConfig('authentication');
// Settle auth URL
$webURL = $config->getConfig("web")->get("base_url");
$auth_selector = $this->config->get("auth_url");
$this->authURL = $webURL . "/" . $auth_selector;
// If the plugin is disabled, stop here
if ($this->config->get('auth_enabled') !== true)
return;
// Fetch Driver type and determine type
/** @var Database $databases */
$databases = Factory::getInstance("databases");
$databaseConnection = $this->config->get("database_group");
$engine = $databases->get($databaseConnection);
if ($engine instanceof PDOEngine)
$driver = Driver::PDO;
elseif ($engine instanceof MongoEngine)
$driver = Driver::MONGO;
else
$driver = Driver::UNKNOWN;
// Load submodules
$this->users = new Users($engine, $driver, $this);
$this->sessions = new Sessions($engine, $driver, $this->config, $this->users);
// Load dependencies which are needed upon every request with the plugin enabled
/** @var Router $router */
$router = Factory::getInstance('router');
// Register route and related views and controllers
$webString = $auth_selector . '(|\/(?P<viewMethod>.*?)(|\/(?P<viewParameters>.*?)))';
$router->addRoute($webString, ['viewType' => 'html', 'viewName' => 'authentication'], Priority::HIGH);
// And register the tracy bridge
if (class_exists('Tracy\Debugger', true))
new AuthenticationTracyBridge($this);
}
/**
* Method that returns on which URL authentication is set to listen to.
*
* By default, this is /auth , but this can be configured in the authentication configuration file.
*
* @return string
*/
public function getAuthenticationURL(): string
{
return $this->authURL;
}
/**
* Returns the authentication configuration file.
*
* @return ConfigORM
*/
public function getConfig(): ConfigORM
{
return $this->config;
}
/**
* @inheritDoc
*/
public function getName(): string
{
return 'auth';
}
/**
* @inheritDoc
*/
public function getClassesPrefix(): ?string
{
return null;
}
/**
* @inheritDoc
*/
public function getSourceDirectory(): ?string
{
return null;
}
/**
* @inheritDoc
*/
public function getPluginClass(): ?string
{
return null;
}
/**
* Returns this plugin in accordance with FuzeWorks\Plugins
*
* @return $this
*/
public function getPlugin(): AuthenticationPlugin
{
return $this;
}
}

View File

@ -0,0 +1,84 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication;
use Tracy\Debugger;
use Tracy\IBarPanel;
/**
* AuthenticationTracyBridge Class.
*
* Integrates the AuthenticationPlugin with the bottom development panel provided by Nette Tracy.
*
* It creates a small icon on the development bar, with info on the current session.
*/
class AuthenticationTracyBridge implements IBarPanel
{
/**
* @var AuthenticationPlugin $plugin
*/
protected AuthenticationPlugin $plugin;
public function __construct(AuthenticationPlugin $plugin)
{
$this->plugin = $plugin;
$bar = Debugger::getBar();
$bar->addPanel($this);
}
/**
* @inheritDoc
*/
function getTab(): string
{
$session = $this->plugin->sessions->getCurrentSession();
ob_start();
require(dirname(__DIR__, 3) . DS . "layout" . DS . "bridge" . DS . "layout.authtab.php");
return ob_get_clean();
}
/**
* @inheritDoc
*/
function getPanel(): string
{
$session = $this->plugin->sessions->getCurrentSession();
ob_start();
require(dirname(__DIR__, 3) . DS . "layout" . DS . "bridge" . DS . "layout.authpanel.php");
return ob_get_clean();
}
}

View File

@ -0,0 +1,46 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication;
/**
* Driver Enum.
*/
enum Driver
{
case PDO;
case MONGO;
case UNKNOWN;
}

View File

@ -0,0 +1,147 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication\Drivers;
use FuzeWorks\Authentication\Model\Session;
use FuzeWorks\Authentication\Model\Users;
use FuzeWorks\Authentication\SessionsModelDriverInterface;
use FuzeWorks\DatabaseEngine\iDatabaseEngine;
use FuzeWorks\DatabaseEngine\MongoEngine;
use MongoDB\Collection;
class MongoSessionsModelDriver implements SessionsModelDriverInterface
{
/** @var MongoEngine */
protected iDatabaseEngine $engine;
protected Collection $collection;
protected Users $usersModel;
public function __construct(iDatabaseEngine $engine, Users $usersModel)
{
$this->engine = $engine;
$this->collection = $this->engine->selectDatabase("auth")->selectCollection("sessions");
$this->usersModel = $usersModel;
}
/**
* @param Session $session
* @return bool
*/
public function createSession(Session $session): bool
{
$insert = [
"sessionKey" => $session->sessionKey,
"user" => $session->user->id,
"expiryDate" => $session->expiryDate,
"expiryThreshold" => $session->expiryThreshold,
"persistent" => $session->persistent,
"active" => $session->active,
"context" => $session->context
];
$res = $this->collection->insertOne($insert);
if ($res->getInsertedCount() !== 1)
return false;
return true;
}
public function readSessions(array $filter): array
{
// Fetch from database
$results = $this->collection->find($filter);
$out = [];
foreach ($results as $result)
{
// Load variables
$sessionKey = $result->sessionKey;
$user = $this->usersModel->getUserByUid($result->user);
$expiryDate = $result->expiryDate;
$expiryThreshold = $result->expiryThreshold;
$persistent = $result->persistent;
$active = $result->active;
$context = (array) $result->context;
$out[] = new Session($user, $sessionKey, $expiryDate, $expiryThreshold, $context, $persistent, $active);
}
// And output cumulative results
return $out;
}
public function updateSessions(array $sessions): bool
{
$modified = 0;
foreach ($sessions as $session)
{
$update = [
"user" => $session->user->id,
"expiryDate" => $session->expiryDate,
"expiryThreshold" => $session->expiryThreshold,
"persistent" => $session->persistent,
"active" => $session->active,
"context" => $session->context
];
$res = $this->collection->updateOne(
["sessionKey" => $session->sessionKey],
['$set' => $update]
);
$modified += $res->getModifiedCount();
}
if ($modified === count($sessions))
return true;
return false;
}
public function deleteSessions(array $sessions): bool
{
$deleted = 0;
foreach ($sessions as $session)
{
$res = $this->collection->deleteOne(["sessionKey" => $session->sessionKey]);
$deleted += $res->getDeletedCount();
}
if ($deleted === count($sessions))
return true;
return false;
}
}

View File

@ -0,0 +1,142 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication\Drivers;
use FuzeWorks\Authentication\Model\User;
use FuzeWorks\Authentication\UsersModelDriverInterface;
use FuzeWorks\DatabaseEngine\iDatabaseEngine;
use FuzeWorks\DatabaseEngine\MongoEngine;
use MongoDB\Collection;
class MongoUsersModelDriver implements UsersModelDriverInterface
{
/** @var MongoEngine */
protected iDatabaseEngine $engine;
protected Collection $collection;
public function __construct(iDatabaseEngine $engine)
{
$this->engine = $engine;
$this->collection = $this->engine->selectDatabase("auth")->selectCollection("users");
}
/**
* @param User[] $users
* @return bool
*/
public function createUsers(array $users): bool
{
$res = $this->collection->insertMany($users);
if ($res->getInsertedCount() == count($users))
return true;
return false;
}
public function readUsers(array $filter, array $options = []): array
{
// Process options
$opt = [];
if (isset($options['limit']))
$opt['limit'] = $options['limit'];
if (isset($options['index']))
$opt['skip'] = $options['index'];
$results = $this->collection->find($filter, $opt);
$out = [];
foreach ($results as $result)
{
$user = new User($result->id);
if (isset($result->primaryEmail))
$user->primaryEmail = $result->primaryEmail;
if (isset($result->password))
$user->password = $result->password;
if (isset($result->permissions))
$user->permissions = (array) $result->permissions;
if (isset($result->active))
$user->active = (bool) $result->active;
if (isset($result->emailVerifyToken))
$user->emailVerifyToken = $result->emailVerifyToken;
if (isset($result->emailVerifyExpiry))
$user->emailVerifyExpiry = $result->emailVerifyExpiry;
if (isset($result->properties))
$user->properties = (array) $result->properties;
$out[] = $user;
}
return $out;
}
/**
* @param User[] $users
* @return bool
*/
public function updateUsers(array $users): bool
{
$modified = 0;
foreach ($users as $user)
{
$res = $this->collection->updateOne(["id" => $user->id], ['$set' => $user]);
$modified += $res->getModifiedCount();
}
if ($modified === count($users))
return true;
return false;
}
/**
* @param User[] $users
* @return bool
*/
public function deleteUsers(array $users): bool
{
$deleted = 0;
foreach ($users as $user)
{
$res = $this->collection->deleteOne(["id" => $user->id]);
$deleted += $res->getDeletedCount();
}
if ($deleted === count($users))
return true;
return false;
}
}

View File

@ -0,0 +1,205 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication\Drivers;
use FuzeWorks\Authentication\Exceptions\AuthenticationException;
use FuzeWorks\Authentication\Model\Session;
use FuzeWorks\Authentication\Model\Users;
use FuzeWorks\Authentication\SessionsModelDriverInterface;
use FuzeWorks\DatabaseEngine\iDatabaseEngine;
use FuzeWorks\DatabaseEngine\PDOEngine;
use FuzeWorks\Factory;
use FuzeWorks\ObjectStorage\ObjectStorageComponent;
use PDO;
class PdoSessionsModelDriver implements SessionsModelDriverInterface
{
/** @var PDOEngine $engine */
protected iDatabaseEngine $engine;
protected Users $usersModel;
protected bool $schema;
public function __construct(iDatabaseEngine $engine, Users $usersModel)
{
$this->engine = $engine;
$this->usersModel = $usersModel;
// Load object storage
/** @var ObjectStorageComponent $storage */
$storage = Factory::getInstance("storage");
$cache = $storage->getCache();
// Lookup schema
$schema = $cache->get("authPdoSessionsSchema");
if (is_null($schema))
{
$this->loadSchema();
$cache->set("authPdoSessionsSchema", true);
}
$this->schema = true;
}
private function loadSchema(): bool
{
// Check if the sessions table exists
$statement = $this->engine->prepare("SELECT * FROM information_schema.tables WHERE `table_name` = :table_name LIMIT 1");
$statement->execute([':table_name' => 'sessions']);
if ($statement->rowCount() !== 1 && !$this->populateSchema())
throw new AuthenticationException("Could not initiate Authentication. Database schema is missing.");
return true;
}
private function populateSchema(): bool
{
// @todo Implement schema populator
return false;
}
public function createSession(Session $session): bool
{
$insert = [
"sessionKey" => $session->sessionKey,
"user" => $session->user->id,
"expiryDate" => $session->expiryDate,
"expiryThreshold" => $session->expiryThreshold,
"persistent" => (int) $session->persistent,
"active" => (int) $session->active,
"context" => json_encode($session->context)
];
// Perform the insert
$statement = $this->engine->prepare("INSERT INTO sessions (sessionKey, user, expiryDate, expiryThreshold, persistent, active, context) VALUES (:sessionKey, :user, :expiryDate, :expiryThreshold, :persistent, :active, :context)");
$statement->execute($insert);
return $statement->rowCount() === 1;
}
public function readSessions(array $filter): array
{
// Prepare the where statement
if (empty($filter))
$where = '';
else
{
$whereKeys = [];
foreach ($filter as $filterKey => $filterVal)
$whereKeys[] = $filterKey . '=:' . $filterKey;
$where = 'WHERE ' . implode(' AND ', $whereKeys);
}
// Prepare the statement
$sql = "SELECT * FROM sessions $where";
$statement = $this->engine->prepare($sql);
// And execute the query
foreach ($filter as $key => $val)
{
if (is_null($val))
$statement->bindValue(':' . $key, $val, PDO::PARAM_NULL);
else
$statement->bindValue(':' . $key, $val);
}
$statement->execute();
// And return the results
$sessions = [];
while ($result = $statement->fetch(PDO::FETCH_OBJ))
{
// Load variables
$sessionKey = $result->sessionKey;
$user = $this->usersModel->getUserByUid($result->user);
$expiryDate = $result->expiryDate;
$expiryThreshold = $result->expiryThreshold;
$persistent = $result->persistent;
$active = $result->active;
$context = json_decode($result->context, true);
$sessions[] = new Session($user, $sessionKey, $expiryDate, $expiryThreshold, $context, $persistent, $active);
}
return $sessions;
}
public function updateSessions(array $sessions): bool
{
// Prepare statement
$statement = $this->engine->prepare("UPDATE sessions SET user=:user, expiryDate=:expiryDate, expiryThreshold=:expiryThreshold, persistent=:persistent, active=:active, context=:context WHERE sessionKey=:sessionKey");
// Loop over sessions
foreach ($sessions as $session)
{
$update = [
"user" => $session->user->id,
"expiryDate" => $session->expiryDate,
"expiryThreshold" => $session->expiryThreshold,
"persistent" => (int) $session->persistent,
"active" => (int) $session->active,
"context" => json_encode($session->context),
"sessionKey" => $session->sessionKey
];
// Perform the update
$statement->execute($update);
if ($statement->rowCount() !== 1)
return false;
}
return true;
}
public function deleteSessions(array $sessions): bool
{
// Prepare statement
$statement = $this->engine->prepare("DELETE FROM sessions WHERE sessionKey=:sessionKey");
// Loop over sessions
foreach ($sessions as $session)
{
// Perform the delete
$statement->execute([':sessionKey' => $session->sessionKey]);
if ($statement->rowCount() !== 1)
return false;
}
return true;
}
}

View File

@ -0,0 +1,449 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication\Drivers;
use FuzeWorks\Authentication\Exceptions\AuthenticationException;
use FuzeWorks\Authentication\Model\User;
use FuzeWorks\Authentication\UsersModelDriverInterface;
use FuzeWorks\DatabaseEngine\iDatabaseEngine;
use FuzeWorks\DatabaseEngine\PDOEngine;
use FuzeWorks\Exception\DatabaseException;
use FuzeWorks\Exception\TransactionException;
use FuzeWorks\Factory;
use FuzeWorks\Logger;
use FuzeWorks\ObjectStorage\ObjectStorageComponent;
use PDO;
class PdoUsersModelDriver implements UsersModelDriverInterface
{
/** @var PDOEngine $engine */
protected iDatabaseEngine $engine;
protected array $schema;
public function __construct(iDatabaseEngine $engine)
{
$this->engine = $engine;
// Load object storage
/** @var ObjectStorageComponent $storage */
$storage = Factory::getInstance("storage");
$cache = $storage->getCache();
// Lookup the current schema
$schema = $cache->get('authPdoUsersSchema');
if (is_null($schema))
{
$schema = $this->loadSchema();
$cache->set('authPdoUsersSchema', $schema, 3600);
}
$this->schema = $schema;
}
/**
* Reads out the database for the current schema. Important to know which columns are available
* in the properties.
*
* @return array
* @throws AuthenticationException
*/
private function loadSchema(): array
{
// Check if the users table exist
$statement = $this->engine->prepare("SELECT * FROM information_schema.tables WHERE `table_name` = :table_name LIMIT 1");
$statement->execute(['table_name' => "users"]);
if ($statement->rowCount() !== 1 && !$this->populateSchema())
throw new AuthenticationException("Could not initiate Authentication. Database schema is missing.");
// Read the properties table and find all existing columns
$statement = $this->engine->prepare("SELECT `column_name` FROM information_schema.columns WHERE `table_name` = :table_name");
$statement->execute(['table_name' => "users_properties"]);
if ($statement->rowCount() < 1)
throw new AuthenticationException("Could not initiate Authentication. Database error.");
// Fetch columns and remove standard columns
$columns = $statement->fetchAll(PDO::FETCH_COLUMN);
$columns = array_splice($columns, 1);
return ['properties' => $columns];
}
private function populateSchema(): bool
{
// @todo Implement schema populator
return false;
}
/**
* Adds the property to the schema. This will add the column to the properties table and save the
* new schema to the cache.
*
* @param string $propertyName
* @param mixed $propertyExample
* @return bool
* @throws DatabaseException
*/
private function addSchemaProperty(string $propertyName, mixed $propertyExample): bool
{
// First check if the property is not already in the schema
$illegal = ['id', 'primaryEmail', 'password', 'mfaSecret', 'permissions', 'active', 'emailVerifyToken', 'emailVerifyExpiry', 'properties', 'user_id'];
if (in_array($propertyName, $illegal))
{
Logger::logError("Could not add property to schema. Illegal property name: " . $propertyName);
return false;
}
// Check the type of the variable
$type = gettype($propertyExample);
switch ($type)
{
case "integer":
$type = "INT";
break;
case "double":
$type = "DOUBLE";
break;
case "string":
$type = "VARCHAR(255)";
break;
case "boolean":
$type = "TINYINT(1)";
break;
default:
Logger::logError("Could not add property to schema. Unknown type: " . $type);
return false;
}
// Add the column to the properties table
$this->engine->query("ALTER TABLE `users_properties` ADD $propertyName $type NULL");
// Save the new schema
$this->schema['properties'][] = $propertyName;
/** @var ObjectStorageComponent $storage */
$storage = Factory::getInstance("storage");
$cache = $storage->getCache();
$cache->set('authPdoUsersSchema', $this->schema, 3600);
return true;
}
/**
* @inheritDoc
*/
public function createUsers(array $users): bool
{
try {
// Prepare statements
$users_sql = "INSERT INTO `users` (id, primaryEmail, password, mfaSecret, permissions, active, emailVerifyToken, emailVerifyExpiry) VALUES (:id, :primaryEmail, :password, :mfaSecret, :permissions, :active, :emailVerifyToken, :emailVerifyExpiry)";
$properties_sql = "INSERT INTO `users_properties` (user_id) VALUES (:id)";
$users_statement = $this->engine->prepare($users_sql);
$properties_statement = $this->engine->prepare($properties_sql);
// Loop over each property
foreach ($users as $user)
foreach ($user->properties as $property => $value)
// Check if the property is already in the schema
if (!in_array($property, $this->schema['properties']))
// Add the property to the schema
if (!$this->addSchemaProperty($property, $value))
throw new DatabaseException("Could not add property to schema.");
// Start transaction
$this->engine->transactionStart();
// Loop over each user
foreach ($users as $user)
{
// First insert the user into the general users table
$users_statement->execute([
"id" => $user->id,
"primaryEmail" => $user->primaryEmail,
"password" => $user->password,
"mfaSecret" => $user->mfaSecret,
"permissions" => implode(";", $user->permissions),
"active" => (int) $user->active,
"emailVerifyToken" => $user->emailVerifyToken,
"emailVerifyExpiry" => $user->emailVerifyExpiry
]);
if ($users_statement->rowCount() !== 1)
{
$this->engine->transactionRollback();
return false;
}
// Then append the user id to the properties table
$properties_statement->execute(['id' => $user->id]);
// Insert all properties
foreach ($user->properties as $propertyKey => $propertyValue)
{
// Insert the property
$statement = $this->engine->prepare("UPDATE `users_properties` SET $propertyKey = :propertyValue WHERE `user_id` = :id");
$statement->execute([
"propertyValue" => $propertyValue,
"id" => $user->id
]);
if ($statement->rowCount() !== 1)
{
$this->engine->transactionRollback();
Logger::logError("Could not create users. Database error.");
return false;
}
}
}
// Commit the transaction
return $this->engine->transactionCommit();
} catch (TransactionException $e) {
Logger::logError("Could not create users. Transaction failure: " . $e->getMessage());
} catch (DatabaseException $e) {
Logger::logError("Could not create users. Database error: " . $e->getMessage());
}
return false;
}
/**
* @inheritDoc
*/
public function readUsers(array $filter, array $options = []): array
{
// Prepare the where statement
if (empty($filter))
$where = '';
else
{
$whereKeys = [];
foreach ($filter as $filterKey => $filterVal)
$whereKeys[] = $filterKey . '=:' . $filterKey;
$where = 'WHERE ' . implode(' AND ', $whereKeys);
}
// Prepare statement
$sql = "SELECT * FROM `users` LEFT JOIN `users_properties` ON users.`id`=users_properties.`user_id` $where";
$statement = $this->engine->prepare($sql);
// And execute the query
foreach ($filter as $key => $val)
{
if (is_null($val))
$statement->bindValue(':' . $key, $val, PDO::PARAM_NULL);
else
$statement->bindValue(':' . $key, $val);
}
// And at last, execute
$statement->execute();
$results = $statement->fetchAll(PDO::FETCH_OBJ);
$out = [];
foreach ($results as $result)
{
$user = new User($result->id);
if (isset($result->primaryEmail))
$user->primaryEmail = $result->primaryEmail;
if (isset($result->password))
$user->password = $result->password;
if (isset($result->mfaSecret))
$user->mfaSecret = $result->mfaSecret;
if (isset($result->permissions))
$user->permissions = explode(";", $result->permissions);
if (isset($result->active))
$user->active = (bool) $result->active;
if (isset($result->emailVerifyToken))
$user->emailVerifyToken = $result->emailVerifyToken;
if (isset($result->emailVerifyExpiry))
$user->emailVerifyExpiry = $result->emailVerifyExpiry;
// And map properties
foreach ($this->schema['properties'] as $property)
if (isset($result->$property))
$user->properties[$property] = $result->$property;
$out[] = $user;
}
return $out;
}
/**
* @inheritDoc
*/
public function updateUsers(array $users): bool
{
try {
// Loop over each property
foreach ($users as $user)
foreach ($user->properties as $property => $value)
// Check if the property is already in the schema
if (!in_array($property, $this->schema['properties']))
// Add the property to the schema
if (!$this->addSchemaProperty($property, $value))
throw new DatabaseException("Could not add property to schema.");
// Start transaction
$this->engine->transactionStart();
foreach ($users as $user)
{
// Get the unaltered user
$oldUser = $this->readUsers(['id' => $user->id])[0];
// Check if there is a difference
$vars = [];
if ($user->primaryEmail != $oldUser->primaryEmail)
$vars['primaryEmail'] = $user->primaryEmail;
if ($user->password != $oldUser->password)
$vars['password'] = $user->password;
if ($user->mfaSecret != $oldUser->mfaSecret)
$vars['mfaSecret'] = $user->mfaSecret;
if ($user->permissions != $oldUser->permissions)
$vars['permissions'] = implode(";", $user->permissions);
if ($user->active != $oldUser->active)
$vars['active'] = (int) $user->active;
if ($user->emailVerifyToken != $oldUser->emailVerifyToken)
$vars['emailVerifyToken'] = $user->emailVerifyToken;
if ($user->emailVerifyExpiry != $oldUser->emailVerifyExpiry)
$vars['emailVerifyExpiry'] = $user->emailVerifyExpiry;
// Prepare statement
$sql = "UPDATE `users` SET " . implode(', ', array_map(function ($key) {
return $key . '=:' . $key;
}, array_keys($vars))) . " WHERE `id`=:id";
$statement = $this->engine->prepare($sql);
// Bind values
foreach ($vars as $key => $val)
$statement->bindValue(':' . $key, $val);
// Bind id
$statement->bindValue(':id', $user->id);
// And execute
if (!empty($vars))
$statement->execute();
// Update all properties
$vars = [];
// First check if any properties of oldUser are removed
foreach ($oldUser->properties as $property => $value)
if (!isset($user->properties[$property]))
$vars[$property] = null;
// Then check if any properties of newUser are added
foreach ($user->properties as $property => $value)
if (!isset($oldUser->properties[$property]) || $oldUser->properties[$property] !== $value)
$vars[$property] = $value;
// If there are no changes, continue
if (empty($vars))
continue;
// Prepare statement
$sql = "UPDATE `users_properties` SET " . implode(', ', array_map(function ($key) {
return $key . '=:' . $key;
}, array_keys($vars))) . " WHERE `user_id`=:id";
$statement = $this->engine->prepare($sql);
// Bind values
foreach ($vars as $key => $val)
$statement->bindValue(':' . $key, $val);
// Bind id
$statement->bindValue(':id', $user->id);
// And execute
$statement->execute();
}
// Commit the transaction
return $this->engine->transactionCommit();
} catch (TransactionException $e) {
Logger::logError("Could not create users. Transaction failure: " . $e->getMessage());
} catch (DatabaseException $e) {
Logger::logError("Could not create users. Database error: " . $e->getMessage());
}
return false;
}
/**
* @inheritDoc
*/
public function deleteUsers(array $users): bool
{
try {
// Start transaction
$this->engine->transactionStart();
// Prepare statements
$statement = $this->engine->prepare("DELETE FROM `users` WHERE `id`=:id");
$statement2 = $this->engine->prepare("DELETE FROM `users_properties` WHERE `user_id`=:id");
// Loop over each user
foreach ($users as $user)
{
// Bind id
$statement->bindValue(':id', $user->id);
$statement2->bindValue(':id', $user->id);
// And execute
$statement2->execute();
$statement->execute();
// Verify that the user was deleted with rowCount
if ($statement->rowCount() !== 1 || $statement2->rowCount() !== 1)
{
$this->engine->transactionRollback();
Logger::logError("Could not delete users. Database error.");
return false;
}
}
// Commit the transaction
return $this->engine->transactionCommit();
} catch (TransactionException $e) {
Logger::logError("Could not delete users. Transaction failure: " . $e->getMessage());
} catch (DatabaseException $e) {
Logger::logError("Could not delete users. Database error: " . $e->getMessage());
}
return false;
}
}

View File

@ -0,0 +1,91 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication\Events;
use FuzeWorks\Event;
class AddUserEvent extends Event
{
public string $email;
public ?string $password;
public bool $active;
protected array $properties;
protected ?string $cancelError = null;
public function __construct(string $email, ?string $password, bool $active, array $parameters)
{
$this->email = $email;
$this->password = $password;
$this->active = $active;
$this->properties = $parameters;
}
public function getProperties(): array
{
return $this->properties;
}
public function setProperty(string $key, mixed $value): void
{
$this->properties[$key] = $value;
}
public function hasProperty(string $key): bool
{
return isset($this->properties[$key]);
}
public function setError(string $errorText)
{
$this->cancelError = $errorText;
}
public function getError(): ?string
{
return $this->cancelError;
}
public function __get(string $key)
{
return $this->properties[$key];
}
public function __set(string $key, mixed $value): void
{
$this->properties[$key] = $value;
}
}

View File

@ -0,0 +1,69 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication\Events;
use FuzeWorks\Event;
use FuzeWorks\Forms\Field;
class GetRegisterFieldsEvent extends Event
{
/**
* @var Field[] $fields
*/
protected array $fields = [];
/**
* @return Field[]
*/
public function getFields(): array
{
return $this->fields;
}
/**
* Add a field to be used on the register page
*
* @param Field $field
* @return void
*/
public function addField(Field $field): void
{
$this->fields[] = $field;
}
}

View File

@ -0,0 +1,66 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication\Events;
use FuzeWorks\Event;
class LoginEvent extends Event
{
public string $identifier;
public string $password;
public bool $remember;
public array $context;
protected array $identifierFields = [];
public function __construct(string $identifier, string $password, bool $remember = false, array $context = [])
{
$this->identifier = $identifier;
$this->password = $password;
$this->remember = $remember;
$this->context = $context;
}
public function addIdentifierField(string $identifierField)
{
$this->identifierFields[] = $identifierField;
}
public function getIdentifierFields(): array
{
return $this->identifierFields;
}
}

View File

@ -0,0 +1,80 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication\Events;
use FuzeWorks\Event;
class RegisterEvent extends Event
{
public string $email;
public ?string $password;
public array $parameters;
public array $properties;
protected ?string $cancelError = null;
public function __construct(string $email, ?string $password, array $parameters, array $properties)
{
$this->email = $email;
$this->password = $password;
$this->parameters = $parameters;
$this->properties = $properties;
}
public function getProperties(): array
{
return $this->properties;
}
public function setProperty(string $key, mixed $value): void
{
$this->properties[$key] = $value;
}
public function hasProperty(string $key): bool
{
return isset($this->properties[$key]);
}
public function setError(string $errorText)
{
$this->cancelError = $errorText;
}
public function getError(): ?string
{
return $this->cancelError;
}
}

View File

@ -0,0 +1,41 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication\Exceptions;
use FuzeWorks\Exception\PluginException;
class AuthenticationException extends PluginException
{
}

View File

@ -0,0 +1,40 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication\Exceptions;
class InputException extends AuthenticationException
{
}

View File

@ -0,0 +1,40 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication\Exceptions;
class LoginErrorException extends LoginException
{
}

View File

@ -0,0 +1,54 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication\Exceptions;
use FuzeWorks\Authentication\AuthenticationPlugin;
use FuzeWorks\Factory;
class LoginException extends AuthenticationException
{
public function __construct(string $message = "", int $code = 0, \Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
// Log the exception
/** @var AuthenticationPlugin $plugin */
$plugin = Factory::getInstance('plugins')->get('auth');
// @todo Log all exceptions of this kind to a log somewhere
}
}

View File

@ -0,0 +1,40 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication\Exceptions;
class LoginWarningException extends LoginException
{
}

View File

@ -0,0 +1,40 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication\Exceptions;
class RegisterErrorException extends AuthenticationException
{
}

View File

@ -0,0 +1,41 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication\Exceptions;
class RegisterWarningException extends AuthenticationException
{
}

View File

@ -0,0 +1,51 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication\Model;
/**
* GuestSession Class.
*
* A special class with the info of guest users, or the data available if sessions are not verified.
*/
class GuestSession extends Session
{
public function __construct()
{
$user = new User("0", "guest@local", null, ["GUEST"], true, null, null, []);
parent::__construct($user, "0", 0, 0, []);
}
}

View File

@ -0,0 +1,121 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication\Model;
class Session
{
/**
* The User this Session pertains to.
*
* @var User
*/
public User $user;
/**
* A 16 character random string identifying this Session.
*
* @var string
*/
public string $sessionKey;
/**
* A unix int timestamp when the session will expire.
*
* @var int
*/
public int $expiryDate;
/**
* A unix int timestamp after which, but before the $expiryDate, a session will be extended.
*
* @var int
*/
public int $expiryThreshold;
/**
* How long a session should last. If false, sessions should only last an hour. If true, sessions should last 3 months.
*
* @var bool
*/
public bool $persistent;
/**
* Other data describing context of the Session, such as User-Agent and IP.
*
* @var array
*/
public array $context;
/**
* Whether this session is active or not.
*
* If false, the user shall be logged out automatically.
*
* @var bool
*/
public bool $active;
/**
* Data about this session stored in ObjectStorage
*
* @var array
*/
protected array $sessionData;
public function __construct(User $user, string $sessionKey, int $expiryDate, int $expiryThreshold, array $context, bool $persistent = false, bool $active = true)
{
$this->user = $user;
$this->sessionKey = $sessionKey;
$this->expiryDate = $expiryDate;
$this->expiryThreshold = $expiryThreshold;
$this->context = $context;
$this->persistent = $persistent;
$this->active = $active;
}
/**
* Returns true if the user of this session has access to a specific permission node.
*
* @param string $permissionString
* @return bool
*/
public function hasPermission(string $permissionString): bool
{
return $this->user->hasPermission($permissionString);
}
}

View File

@ -0,0 +1,272 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication\Model;
use FuzeWorks\Authentication\Driver;
use FuzeWorks\Authentication\Drivers\MongoSessionsModelDriver;
use FuzeWorks\Authentication\Drivers\PdoSessionsModelDriver;
use FuzeWorks\Authentication\SessionsModelDriverInterface;
use FuzeWorks\Config;
use FuzeWorks\ConfigORM\ConfigORM;
use FuzeWorks\DatabaseEngine\iDatabaseEngine;
use FuzeWorks\Exception\FactoryException;
use FuzeWorks\Factory;
use FuzeWorks\Input;
use FuzeWorks\Logger;
use FuzeWorks\Model;
class Sessions extends Model
{
protected ?SessionsModelDriverInterface $driver;
protected ConfigORM $authCFG;
protected ConfigORM $webCFG;
protected Users $usersModel;
/**
* The current session
*
* @var Session
*/
protected Session $session;
public function __construct(iDatabaseEngine $engine, Driver $driver, ConfigORM $config, Users $usersModel)
{
parent::__construct();
// Load engine
if ($driver == Driver::PDO)
$this->driver = new PdoSessionsModelDriver($engine, $usersModel);
elseif ($driver == Driver::MONGO)
$this->driver = new MongoSessionsModelDriver($engine, $usersModel);
// Load config
$this->authCFG = $config;
$this->webCFG = Factory::getInstance("config")->getConfig("web");
// Save Users
$this->usersModel = $usersModel;
// And load the guest session
$this->session = new GuestSession();
}
/**
* @param string|null $sessionKey
* @return Session
*/
public function start(string $sessionKey = null): Session
{
// Fetch the sessionKey
if (is_null($sessionKey))
{
/** @var Input $input */
$input = Factory::getInstance("input");
$cookieName = $this->webCFG->get("cookie_prefix") . "fw_auth_token";
$sessionKey = $input->cookie($cookieName);
}
// If the sessionKey is still null, return the current GuestSession
if (is_null($sessionKey))
return $this->session;
// Otherwise, fetch the current session from the database
$session = $this->getSessionByHash($sessionKey);
if (is_null($session))
{
// Remove the session cookie
$this->_setSession("", 0);
return $this->session;
}
// Check if the date has expired
if (date('U') > $session->expiryDate)
{
Logger::log("Current session has expired. Ending session.");
$session->active = false;
if (!$this->updateSession($session))
Logger::logError("Failed to update session. Setting guest session for now.");
// And remove the session cookie
$this->_setSession("", 0);
return $this->session;
}
// If session has been deactivated, remove cookie and return guest session
if (!$session->active)
{
$this->_setSession("", 0);
return $this->session;
}
// If user has been deactivated, update session and remove cookie
if (!$session->user->active)
{
Logger::log("Current user has been deactivated. Ending session.");
$session->active = false;
if (!$this->updateSession($session))
Logger::logError("Failed to update session. Setting guest session for now.");
// And remove the session cookie
$this->_setSession("", 0);
return $this->session;
}
// Verify that email has been verified on time
if (!is_null($session->user->emailVerifyToken) && date('U') > $session->user->emailVerifyExpiry)
{
// Deactivate the user and write to database
Logger::log("User must verify email before continuing.");
$this->_setSession("", 0);
return $this->session;
}
// Extend the session if threshold has expired
if (date('U') > $session->expiryThreshold)
{
$session->expiryDate = ($session->persistent ? (int) date('U') + (3600*24*31*3) : (int) date('U') + 3600);
$session->expiryThreshold = ($session->persistent ? (int) date('U') + (3600*24) : (int) date('U') + 900);
Logger::log("Current session needs to be extended. Extending session.");
if (!$this->updateSession($session))
Logger::logError("Failed to update session. Maintaining current session.");
// Extend the session
$this->_setSession($session->sessionKey, $session->expiryDate);
}
// Log the session
$id = !is_null($session->user->primaryEmail) ? $session->user->primaryEmail : $session->user->id;
Logger::log("Current session is: '" . $id . "'");
// Set the values and return the session
$this->session = $session;
return $this->session;
}
/**
* @param string $sessionKey
* @return Session|null
*/
public function getSessionByHash(string $sessionKey): ?Session
{
$sessions = $this->driver->readSessions(["sessionKey" => $sessionKey]);
if (empty($sessions))
return null;
return $sessions[0];
}
/**
* @param string $contextKey
* @param string $contextValue
* @return Session[]
*/
public function getSessionsByContext(string $contextKey, string $contextValue): array
{
return $this->driver->readSessions(["context." . $contextKey => $contextValue]);
}
/**
* @param array $filter
* @return Session[]
*/
public function getSessions(array $filter): array
{
return $this->driver->readSessions($filter);
}
public function createSession(User $user, array $context, bool $persistent = false, bool $active = true): ?Session
{
// Generate variables
$hash = substr(base64_encode(sha1(mt_rand())), 0, 16);
$expire = ($persistent ? (int) date('U') + (3600*24*31*3) : (int) date('U') + 3600);
$threshold = ($persistent ? (int) date('U') + (3600*24) : (int) date('U') + 900);
// Create Model
$session = new Session($user, $hash, $expire, $threshold, $context, $persistent, $active);
// Write to driver and set cookie
if ($this->driver->createSession($session))
{
$this->_setSession($hash, $expire);
return $session;
}
return null;
}
public function updateSession(Session $session): bool
{
return $this->driver->updateSessions([$session]);
}
public function endSession(Session $session): bool
{
$session->active = false;
if ($this->updateSession($session))
{
$this->_setSession("", 0);
return true;
}
return false;
}
public function deleteSession(Session $session): bool
{
return $this->driver->deleteSessions([$session]);
}
public function getCurrentSession(): Session
{
return $this->session;
}
private function _setSession(string $sessionKey, int $expire)
{
// @todo Update to use FuzeWorks\Output
setcookie(
$this->webCFG->get("cookie_prefix") . "fw_auth_token",
$sessionKey,
$expire,
$this->webCFG->get("cookie_path"),
$this->webCFG->get("cookie_domain"),
$this->webCFG->get("cookie_secure"),
$this->webCFG->get("cookie_httponly")
);
}
}

View File

@ -0,0 +1,146 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication\Model;
class User
{
/**
* A 16 character random id of the user.
*
* @var string
*/
public string $id;
/**
* The primary identifier for the user.
*
* Emails will get sent to this address.
*
* @var string|null
*/
public ?string $primaryEmail;
/**
* The password of the user, as created by the password_hash() function.
*
* @var string|null
*/
public ?string $password;
/**
* A secret key used to generate TOTP tokens.
*
* @var string|null
*/
public ?string $mfaSecret = null;
/**
* Separated permission nodes this user has access to.
*
* @var array
*/
public array $permissions = [];
/**
* Whether this user is active or not.
*
* If false, user should be blocked from logging in or continuing a session.
*
* @var bool
*/
public bool $active;
/**
* A 16 character random token used to verify the email belongs to the user.
*
* @var string|null
*/
public ?string $emailVerifyToken;
/**
* A unix int timestamp before which time the user needs to have verified their email.
*
* @var int|null
*/
public ?int $emailVerifyExpiry;
/**
* A key => value list of other properties describing the user, such as username or social info.
*
* @var array
*/
public array $properties = [];
public function __construct(string $id, string $primaryEmail = null, string $password = null, array $permissions = [], bool $active = true, string $emailToken = null, int $emailExpire = null, array $properties = [])
{
$this->id = $id;
$this->primaryEmail = $primaryEmail;
$this->password = $password;
$this->permissions = $permissions;
$this->active = $active;
$this->emailVerifyToken = $emailToken;
$this->emailVerifyExpiry = $emailExpire;
$this->properties = $properties;
}
/**
* Returns true if the user has access to a specific permission node.
*
* @param string $permissionTag
* @return bool
*/
public function hasPermission(string $permissionTag): bool
{
return in_array(strtoupper($permissionTag), $this->permissions) || in_array("SUPER", $this->permissions);
}
public function __isset(string $name)
{
return isset($this->properties[$name]);
}
public function __get(string $name)
{
return $this->properties[$name];
}
public function __set(string $name, mixed $value)
{
$this->properties[$name] = $value;
}
}

View File

@ -0,0 +1,279 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication\Model;
use FuzeWorks\Authentication\AuthenticationPlugin;
use FuzeWorks\Authentication\Driver;
use FuzeWorks\Authentication\Drivers\MongoUsersModelDriver;
use FuzeWorks\Authentication\Drivers\PdoUsersModelDriver;
use FuzeWorks\Authentication\Events\AddUserEvent;
use FuzeWorks\Authentication\Exceptions\AuthenticationException;
use FuzeWorks\Authentication\Exceptions\InputException;
use FuzeWorks\Authentication\UsersModelDriverInterface;
use FuzeWorks\ConfigORM\ConfigORM;
use FuzeWorks\DatabaseEngine\iDatabaseEngine;
use FuzeWorks\Events;
use FuzeWorks\Exception\FactoryException;
use FuzeWorks\Exception\LayoutException;
use FuzeWorks\Factory;
use FuzeWorks\Layout;
use FuzeWorks\Mailer\PHPMailerWrapper;
use FuzeWorks\Model;
use PHPMailer\PHPMailer\Exception;
class Users extends Model
{
protected ?UsersModelDriverInterface $driver;
protected ConfigORM $authCFG;
protected AuthenticationPlugin $plugin;
public function __construct(iDatabaseEngine $engine, Driver $driver, AuthenticationPlugin $plugin)
{
parent::__construct();
// Determine driver
// @todo Implement PDO
if ($driver == Driver::PDO)
$this->driver = new PdoUsersModelDriver($engine);
elseif ($driver == Driver::MONGO)
$this->driver = new MongoUsersModelDriver($engine);
// Set plugin
$this->plugin = $plugin;
// Load config
$this->authCFG = $plugin->config;
}
/**
* @param string $email
* @return User|null
*/
public function getUserByEmail(string $email): ?User
{
$users = $this->driver->readUsers(["primaryEmail" => $email]);
if (empty($users))
return null;
return $users[0];
}
/**
* @param string $uid
* @return User|null
*/
public function getUserByUid(string $uid): ?User
{
$users = $this->driver->readUsers(["id" => $uid]);
if (empty($users))
return null;
return $users[0];
}
/**
* @param string[] $ids
* @return User[]
*/
public function getUsersByUids(array $ids): array
{
$out = [];
foreach ($ids as $id)
{
$user = $this->getUserByUid($id);
if (!is_null($user))
$out[] = $user;
}
return $out;
}
/**
* Returns all the users which have a specific permission node.
*
* @note Currently non-functional on PDO backends
* @todo Fix^
*
* @param string $permissionString
* @return User[]
*/
public function getUsersByPermissionString(string $permissionString): array
{
return $this->driver->readUsers(["permissions" => $permissionString]);
}
/**
* @param string $variableKey
* @param string $variableValue
* @return User[]
*/
public function getUsersByProperty(string $variableKey, string $variableValue): array
{
return $this->driver->readUsers(["properties.".$variableKey => $variableValue]);
}
/**
* @param string $variableKey
* @param string $variableValue
* @return User[]
*/
public function getUsersByVariable(string $variableKey, string $variableValue): array
{
return $this->driver->readUsers([$variableKey => $variableValue]);
}
/**
* Retrieve all users
*
* @return Users[]
*/
public function getUsers(int $index = 0, int $limit = 999): array
{
return $this->driver->readUsers([], ['index' => $index, 'limit' => $limit]);
}
/**
* @param string $email
* @param string|null $password
* @param bool $active
* @param array $properties
* @return bool
* @throws InputException
* @throws AuthenticationException
*/
public function addUser(string $email, ?string $password, bool $active = true, array $properties = []): bool
{
// First verify the variables
if (!is_null($password))
{
$this->validatePassword($password);
// Hash the password
$password = password_hash($password, $this->authCFG->get("password_algorithm"), $this->authCFG->get("password_options"));
}
// Generate uid
$uid = substr(base64_encode(sha1(mt_rand())), 0, 16);
// Verify if a user with this identifier already exists
$this->validateEmail($email);
if ($this->getUserByEmail($email) !== null)
throw new InputException("This user already exists.");
// Fire AddUserEvent
/** @var AddUserEvent $event */
$event = Events::fireEvent(new AddUserEvent($email, $password, $active, $properties));
if ($event->isCancelled())
{
$error = is_null($event->getError()) ? "Add user cancelled by system." : $event->getError();
throw new InputException($error);
}
// Prepare user verification code
$emailExpire = (int) date('U') + (int) $this->authCFG->get("verifyEmailWithin");
$emailToken = substr(base64_encode(sha1(mt_rand())), 0, 16);
// Prepare user object
$user = new User($uid, $event->email, $event->password, ["USER"], $event->active, $emailToken, $emailExpire, $event->getProperties());
// Send the data to the driver
return $this->driver->createUsers([$user]);
}
/**
* @param User $user
* @param string $newPassword
* @return bool
* @throws InputException
*/
public function changePassword(User $user, string $newPassword): bool
{
// Validate the password
$this->validatePassword($newPassword);
// Calculate password
$user->password = password_hash($newPassword, $this->authCFG->get("password_algorithm"), $this->authCFG->get("password_options"));
// And commit
return $this->updateUser($user);
}
public function updateUser(User $user): bool
{
return $this->driver->updateUsers([$user]);
}
public function deleteUser(User $user): bool
{
return $this->driver->deleteUsers([$user]);
}
/**
* Validate if the password is strong enough
*
* @param string $password
* @return bool
* @throws InputException
*/
protected function validatePassword(string $password): bool
{
// First check for length
$min_length = $this->authCFG->get('password_min_length');
if (strlen($password) < $min_length)
throw new InputException("Password is too short. Must contain at least $min_length characters.");
// @todo Implement more tests
return true;
}
/**
* Validate if the provided string is a valid email.
*
* @param string $email
* @return bool
* @throws InputException
*/
protected function validateEmail(string $email): bool
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL))
throw new InputException("Provided email is not valid.");
return true;
}
}

View File

@ -0,0 +1,86 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication;
use FuzeWorks\Authentication\Model\Session;
use FuzeWorks\Authentication\Model\Users;
use FuzeWorks\DatabaseEngine\iDatabaseEngine;
interface SessionsModelDriverInterface
{
/**
* Initializes the Model.
*
* Should accept the Engine and UsersModel.
*
* @param iDatabaseEngine $engine
* @param Users $usersModel
*/
public function __construct(iDatabaseEngine $engine, Users $usersModel);
/**
* Add a new Session to the database using the provided Session object.
*
* @param Session $session
* @return bool
*/
public function createSession(Session $session): bool;
/**
* Return a list of Session[] from the database on the basis of the provided search filter.
*
* @param array $filter
* @return Session[]
*/
public function readSessions(array $filter): array;
/**
* Update a list of Session[] in the database on the basis of the provided Session[] objects.
*
* @param Session[] $sessions
* @return bool
*/
public function updateSessions(array $sessions): bool;
/**
* Delete the provided Session[] objects from the database.
*
* @param Session[] $sessions
* @return bool
*/
public function deleteSessions(array $sessions): bool;
}

View File

@ -0,0 +1,85 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication;
use FuzeWorks\Authentication\Exceptions\InputException;
use FuzeWorks\Authentication\Model\User;
use FuzeWorks\DatabaseEngine\iDatabaseEngine;
interface UsersModelDriverInterface
{
/**
* Initializes the model.
*
* Should only require the provided Database Engine.
*
* @param iDatabaseEngine $engine
*/
public function __construct(iDatabaseEngine $engine);
/**
* Add a new Session to the database using the provided User object.
*
* @param User[] $users
* @return bool
*/
public function createUsers(array $users): bool;
/**
* Return a list of User[] from the database on the basis of the provided search filter.
*
* @return User[]
*/
public function readUsers(array $filter, array $options = []): array;
/**
* Update a list of User[] in the database on the basis of the provided User[] objects.
*
* @param User[] $users
* @return bool
*/
public function updateUsers(array $users): bool;
/**
* Delete the provided User[] objects from the database.
*
* @param User[] $users
* @return bool
*/
public function deleteUsers(array $users): bool;
}

56
test/base/PluginTest.php Normal file
View File

@ -0,0 +1,56 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (http://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @link https://i15.nl
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
use FuzeWorks\Authentication\AuthenticationPlugin;
use FuzeWorks\Factory;
use PHPUnit\Framework\TestCase;
class PluginTest extends TestCase
{
private AuthenticationPlugin $plugin;
public function setUp(): void
{
parent::setUp();
$this->plugin = Factory::getInstance('plugins')->get('authentication');
}
public function testFoundation()
{
$this->assertInstanceOf(AuthenticationPlugin::class, $this->plugin);
}
}

65
test/bootstrap.php Normal file
View File

@ -0,0 +1,65 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
require_once(dirname(__DIR__) . '/vendor/autoload.php');
use FuzeWorks\Configurator;
$configurator = new Configurator();
// Set directories
$configurator->setTempDirectory(__DIR__ . '/temp');
$configurator->setLogDirectory(__DIR__ . '/temp');
// Other values
$configurator->setTimeZone('Europe/Amsterdam');
// Add config folder
$configurator->addDirectory(dirname(__FILE__), 'config');
// Add components
$configurator->addComponent(new \FuzeWorks\WebComponent());
$configurator->addComponent(new \FuzeWorks\ObjectStorage\ObjectStorageComponent());
$configurator->addComponent(new \FuzeWorks\DatabaseComponent());
$configurator->addComponent(new \FuzeWorks\LayoutComponent());
$configurator->addComponent(new \FuzeWorks\TracyComponent());
// Build the container
$container = $configurator->createContainer();
// And add the library
$container->libraries->addLibraryClass("forms", \FuzeWorks\Forms\Forms::class);
$container->plugins->addPlugin(new \FuzeWorks\Authentication\AuthenticationPlugin());
return $container;

56
test/config.database.php Normal file
View File

@ -0,0 +1,56 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
use FuzeWorks\Core;
return [
'active_group' => Core::getEnv("DATABASE_DRIVER", "pdo"),
'connections' => [
'pdo' => [
'engineName' => 'pdo',
'dsn' => Core::getEnv("PDO_DSN", Core::getEnv("PDO_DRIVER", "mysql") . ":host=" . Core::getEnv("PDO_HOST", "127.0.0.1") . ";dbname=" . Core::getEnv("PDO_DATABASE", "") . ";charset=utf8"),
'username' => Core::getEnv("PDO_USERNAME", ""),
'password' => Core::getEnv("PDO_PASSWORD", "")
],
'mongo' => [
'engineName' => 'mongo',
'uri' => Core::getEnv("MONGO_URI", "mongodb://" . Core::getEnv("MONGO_HOST", "127.0.0.1") . ":" . Core::getEnv("MONGO_PORT", "27017")),
'uriOptions' => [],
'driverOptions' => [],
'username' => Core::getEnv("MONGO_USERNAME"),
'password' => Core::getEnv("MONGO_PASSWORD")
]
]
];

60
test/config.objectstorage.php Executable file
View File

@ -0,0 +1,60 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
use FuzeWorks\Core;
return [
// Which provider shall be used
// Options: DummyProvider, RedisProvider, FileProvider
'ObjectStorageProvider' => 'DummyProvider',
'DummyProvider' => [],
'RedisProvider' => [
// Type can be 'tcp' or 'unix'
'socket_type' => Core::getEnv('OBJECTSTORAGE_REDIS_SOCKET_TYPE', 'tcp'),
// If socket_type == 'unix', set the socket here
'socket' => Core::getEnv('OBJECTSTORAGE_REDIS_SOCKET', null),
// If socket_type == 'tcp', set the host here
'host' => Core::getEnv('OBJECTSTORAGE_REDIS_HOST', '127.0.0.1'),
// And some standard settings
'port' => Core::getEnv('OBJECTSTORAGE_REDIS_PORT', 6379),
'password' => Core::getEnv('OBJECTSTORAGE_REDIS_PASSWORD', null),
'timeout' => Core::getEnv('OBJECTSTORAGE_REDIS_TIMEOUT', 0),
'db_index' => Core::getEnv('OBJECTSTORAGE_REDIS_DBINDEX', 0),
],
'FileProvider' => [
// The directory where objects get stored by the FileProvider
'storage_directory' => Core::getEnv('OBJECTSTORAGE_FILE_DIRECTORY', Core::$tempDir)
]
];

17
test/phpunit.xml Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" bootstrap="bootstrap.php" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" stopOnError="false" stopOnFailure="false" stopOnIncomplete="false" stopOnSkipped="false" colors="false">
<coverage processUncoveredFiles="false">
<include>
<directory suffix=".php">../</directory>
</include>
<exclude>
<directory suffix=".php">../vendor/</directory>
<directory suffix=".php">../test/</directory>
</exclude>
</coverage>
<testsuites>
<testsuite name="Authentication Suite">
<directory>./</directory>
</testsuite>
</testsuites>
</phpunit>

2
test/temp/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -0,0 +1,144 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace Application\View;
use Application\Controller\AuthenticationController;
use FuzeWorks\Administration\AdminView;
use FuzeWorks\Administration\Attributes\DisplayAttribute;
use FuzeWorks\Administration\Attributes\HiddenAttribute;
use FuzeWorks\Administration\Attributes\IconAttribute;
use FuzeWorks\Administration\Attributes\PermissionAttribute;
use FuzeWorks\Administration\Attributes\PriorityAttribute;
use FuzeWorks\Authentication\Events\GetRegisterFieldsEvent;
use FuzeWorks\Authentication\Exceptions\RegisterErrorException;
use FuzeWorks\Authentication\Exceptions\RegisterWarningException;
use FuzeWorks\Controller;
use FuzeWorks\Events;
use FuzeWorks\Forms\Fields\CheckboxField;
use FuzeWorks\Forms\Fields\EmailField;
use FuzeWorks\Forms\Fields\PasswordField;
use FuzeWorks\Forms\Fields\SubmitField;
use FuzeWorks\Forms\Form;
use FuzeWorks\Forms\Forms;
use FuzeWorks\Priority;
class AuthenticationAdminView extends AdminView
{
/** @var AuthenticationController $controller */
protected Controller $controller;
#[DisplayAttribute("Users"), IconAttribute("users"), PriorityAttribute(Priority::LOW)]
#[PermissionAttribute(["ADMIN"])]
public function users()
{
// List all users
$users = $this->controller->listUsers();
$this->layouts->assign("users", $users);
return $this->layouts->get("admin/users_list");
}
#[HiddenAttribute]
#[PermissionAttribute(["ADMIN"])]
public function view(string $userId)
{
$info = $this->controller->getDetailedUserInformation($userId);
dump($info);
exit;
return $userId;
}
#[HiddenAttribute]
#[DisplayAttribute("Create User Account")]
public function create()
{
// Prepare form
/** @var Forms $forms */
$forms = $this->libraries->get("forms");
$form = $forms->getCachedForm(function (Form $form){
// First prepare the identifier field
$field1 = new EmailField("identifier");
$field1->setLabel("Email address")
->placeholder("Email");
$form->field($field1);
// Add additional fields through GetRegisterFieldsEvent
/** @var GetRegisterFieldsEvent $event */
$event = Events::fireEvent(new GetRegisterFieldsEvent());
foreach ($event->getFields() as $field)
{
$field->setNote("eventField");
$form->field($field);
}
// Terms and conditions field
$field4 = new CheckboxField("sendemail");
$field4->setLabel("Send User Registration Notification");
$field4->checked();
$form->field($field4);
// Submit field
$field5 = new SubmitField("submit");
$field5->setButtonText("Create User Account");
$form->field($field5);
return $form;
}, "AuthCreateUserForm", "Register");
// After validation, send data to the controller
if ($form->validate())
{
// Prepare variables
$identifier = $form->getField("identifier")->getValue();
// Then forward the form to the controller
try {
$this->controller->register($identifier, null, [], [], true);
} catch (RegisterErrorException $e) {
$form->invalidate(true);
$form->addFormError($e->getMessage());
} catch (RegisterWarningException $e) {
$form->invalidate(true);
$form->addFormWarning($e->getMessage());
}
}
$this->layouts->assign("form", $form);
return $this->layouts->get("admin/users_create");
}
}

414
view/view.html.authentication.php Executable file
View File

@ -0,0 +1,414 @@
<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace Application\View;
use Application\Controller\AuthenticationController;
use FuzeWorks\Authentication\AuthenticationPlugin;
use FuzeWorks\Authentication\Events\GetRegisterFieldsEvent;
use FuzeWorks\Authentication\Exceptions\LoginErrorException;
use FuzeWorks\Authentication\Exceptions\LoginWarningException;
use FuzeWorks\Authentication\Exceptions\RegisterErrorException;
use FuzeWorks\Authentication\Exceptions\RegisterWarningException;
use FuzeWorks\ConfigORM\ConfigORM;
use FuzeWorks\Controller;
use FuzeWorks\Event\LayoutLoadEvent;
use FuzeWorks\Events;
use FuzeWorks\Factory;
use FuzeWorks\Forms\Fields\CheckboxField;
use FuzeWorks\Forms\Fields\DecoratorField;
use FuzeWorks\Forms\Fields\EmailField;
use FuzeWorks\Forms\Fields\PasswordField;
use FuzeWorks\Forms\Fields\SubmitField;
use FuzeWorks\Forms\Fields\TextField;
use FuzeWorks\Forms\Form;
use FuzeWorks\Forms\Forms;
use FuzeWorks\Forms\Method;
use FuzeWorks\Layout;
use FuzeWorks\WebView;
use OTPHP\TOTP;
class AuthenticationHtmlView extends WebView
{
/**
* @var AuthenticationController
*/
protected Controller $controller;
protected Layout $layouts;
protected AuthenticationPlugin $plugin;
protected Forms $forms;
public function __construct()
{
parent::__construct();
$this->layouts = Factory::getInstance("layouts");
// Get the authentication config
$this->plugin = $this->plugins->get('auth');
$this->forms = $this->libraries->get("forms");
// And assign all related layout variables
$this->assignLayoutVariables();
}
public function login()
{
// If the current user is already logged in, redirect
$location = $this->input->get("location");
$redirect = is_null($location) ? "/" : $location;
if ($this->controller->session->user->id !== "0")
$this->output->setHeader("Location: " . $redirect);
// Prepare form
$form = $this->forms->getCachedForm(function (Form $form) {
// First prepare the identifier field
$field1 = new TextField("identifier");
$field1->setLabel("Username")->placeholder("Username or email");
$form->field($field1);
// Then prepare the password field
$field2 = new PasswordField("password");
$field2->setLabel("Password")->placeholder("Password");
$form->field($field2);
// Forgot password link
if ($this->plugin->config->forgot_password_enabled)
{
$url = $this->plugin->getAuthenticationURL();
$forgotHtml = "<a href='$url/forgot_password'>Forgot password?</a>";
$forgot = new DecoratorField("forgot", $forgotHtml);
$form->field($forgot);
}
// Then prepare remember me field
$field3 = new CheckboxField("remember");
$field3->setLabel("Remember me");
$form->field($field3);
// Submit field
$field4 = new SubmitField("submit");
$field4->setButtonText("Login")->addClass("btn-success")->addClass("btn-block");
$form->field($field4);
return $form;
}, "AuthLoginForm", "Login");
// Validate the form
if ($form->validate()) {
// Send data to the controller
try {
// Collect context
$context = [
'userAgent' => $this->input->userAgent(),
'remoteAddr' => $this->input->server("REMOTE_ADDR")
];
// Attempt to login. If it fails, will be handled by exception handlers.
$this->controller->login($form->identifier->getValue(), $form->password->getValue(), $form->remember->getValue(), $context);
// And redirect the user
$this->output->setHeader("Location: " . $redirect);
} catch (LoginErrorException $e) {
$form->invalidate(true);
$form->addFormError($e->getMessage());
} catch (LoginWarningException $e) {
$form->invalidate(true);
$form->addFormWarning($e->getMessage());
}
}
// Assign the form and display the page
$this->layouts->assign("form", $form);
$this->layouts->display("pages/auth_form_page");
}
public function logout()
{
$this->controller->logout();
$location = $this->input->get("location");
$redirect = is_null($location) ? "/" : $location;
$this->output->setHeader("Location: " . $redirect);
}
public function register()
{
// First check if the registration is enabled
if (!$this->plugin->getConfig()->get("register_enabled"))
$this->output->setHeader("Location: /");
if ($this->controller->session->user->id !== "0")
$this->output->setHeader("Location: /");
// Prepare form
$form = $this->forms->getCachedForm(function (Form $form) {
// First prepare the identifier field
$field1 = new EmailField("identifier");
$field1->setLabel("Email address")
->placeholder("Email");
$form->field($field1);
// Add additional fields through GetRegisterFieldsEvent
/** @var GetRegisterFieldsEvent $event */
$event = Events::fireEvent(new GetRegisterFieldsEvent());
foreach ($event->getFields() as $field) {
$field->setNote("eventField");
$form->field($field);
}
// Add password fields
$field2 = new PasswordField("password1");
$field3 = new PasswordField("password2");
$field2->setLabel("Password")
->placeholder("Password")
->minLength($this->controller->authConfig->get("password_min_length"));
$field3->setLabel("Repeat password")
->placeholder("Password")
->minLength($this->controller->authConfig->get("password_min_length"));
$form->field($field2);
$form->field($field3);
// Terms and conditions field
$field4 = new CheckboxField("terms");
$field4->setLabel("Accept Terms and Conditions");
$field4->required("You must accept the terms and conditions.");
$form->field($field4);
// Submit field
$field5 = new SubmitField("submit");
$field5->setButtonText("Register")->addClass("btn-success")->addClass("btn-block");
$form->field($field5);
// And add a password equivalency check
$form->condition([AuthenticationController::class, "registerFormCondition"]);
return $form;
}, "AuthRegisterForm", "Register");
// After validation, send data to controller
if ($form->validate()) {
// Prepare variables
$identifier = $form->getField("identifier")->getValue();
$password = $form->getField("password1")->getValue();
$properties = [];
foreach ($form->getFields() as $field)
if ($field->getNote() === "eventField")
$properties[$field->getName()] = $field->getValue();
// Then forward the form to the controller
try {
$this->controller->register($identifier, $password, [], $properties);
} catch (RegisterErrorException $e) {
$form->invalidate(true);
$form->addFormError($e->getMessage());
} catch (RegisterWarningException $e) {
$form->invalidate(true);
$form->addFormWarning($e->getMessage());
}
}
$this->layouts->assign("form", $form);
$this->layouts->display("pages/auth_form_page");
}
public function resend_verify_email()
{
// Get token
$token = $this->input->get("t");
// If the token is empty, report so
if (empty($token))
$this->output->setHeader("Location: /");
// Generate basic form
$form = new Form("ResendVerifyEmailForm", "Resend verification email");
$form->method(Method::GET);
$form->validate();
// Extract email
$email = base64_decode($token);
// Resend verification email using the controller
try {
$this->controller->resendVerifyEmail($email);
return "A new verification email has been sent to your email address.";
} catch (LoginErrorException $e) {
$form->invalidate(true);
$form->addFormError($e->getMessage());
} catch (LoginWarningException $e) {
$form->invalidate(true);
$form->addFormWarning($e->getMessage());
}
$this->layouts->assign("form", $form);
$this->layouts->display("pages/auth_form_page");
}
public function verify(): string
{
// Fetch token
$token = $this->input->get("t");
// If the token is empty, report so
if (is_null($token))
return "No token provided.";
// Verify with controller
$user = $this->controller->verifyEmail($token);
if (is_null($user))
return "Token invalid";
// If no password has been set, redirect the user to set a password
if (is_null($user->password))
{
$token = $this->controller->createResetToken($user);
$this->output->setHeader("Location: " . $this->plugin->getAuthenticationURL() . "/reset_password?t=" . $token);
}
else
$this->output->setHeader("Location: " . $this->plugin->getAuthenticationURL());
return "Token verified";
}
public function forgot_password()
{
// If user is already logged in, redirect to homepage
if ($this->controller->session->user->id !== "0")
$this->output->setHeader("Location: /");
// Check if forgot password is disabled
if (!$this->plugin->getConfig()->get("forgot_password_enabled"))
$this->output->setHeader("Location: /");
// Prepare form
$form = $this->forms->getCachedForm(function (Form $form){
// First prepare the identifier field
$field1 = new EmailField("identifier");
$field1->setLabel("Email address")
->placeholder("Email");
$form->field($field1);
// Submit field
$field4 = new SubmitField("submit");
$field4->setButtonText("Send Account Recovery Email");
$form->field($field4);
return $form;
}, "AuthForgotPasswordForm", "Forgot password");
if ($form->validate())
{
// Fetch identifier
$identifier = $form->getField("identifier")->getValue();
// Request a password reset
try {
$this->controller->forgotPassword($identifier);
} catch (LoginErrorException $e) {
$form->invalidate(true);
$form->addFormError($e->getMessage());
} catch (LoginWarningException $e) {
$form->invalidate(true);
$form->addFormWarning($e->getMessage());
}
}
$this->layouts->assign("form", $form);
$this->layouts->display("pages/auth_form_page");
}
public function reset_password()
{
$form = $this->forms->getCachedForm(function (Form $form){
// Add password fields
$field2 = new PasswordField("password1");
$field3 = new PasswordField("password2");
$field2->setLabel("Password")
->placeholder("Password")
->minLength($this->controller->authConfig->get("password_min_length"));
$field3->setLabel("Repeat password")
->placeholder("Password")
->minLength($this->controller->authConfig->get("password_min_length"));
$form->field($field2);
$form->field($field3);
// Submit field
$field4 = new SubmitField("submit");
$field4->setButtonText("Reset Password");
$form->field($field4);
// And add a password equivalency check
$form->condition([AuthenticationController::class, "registerFormCondition"]);
return $form;
}, "AuthResetPasswordForm", "Reset Password");
if ($form->validate())
{
// Fetch token
$token = $this->input->get("t");
$password = $form->getField("password1")->getValue();
// Reset by controller
try {
$this->controller->resetPassword($token, $password);
$this->output->setHeader("Location: " . $this->plugin->getAuthenticationURL() . "/login");
} catch (LoginErrorException $e) {
$form->invalidate(true);
$form->addFormError($e->getMessage());
} catch (LoginWarningException $e) {
$form->invalidate(true);
$form->addFormWarning($e->getMessage());
}
}
$this->layouts->assign("form", $form);
$this->layouts->display("pages/auth_form_page");
}
/**
* Assign all view related variables to the layouts
*
* @return void
*/
private function assignLayoutVariables(): void
{
$config = $this->plugin->getConfig();
$this->layouts->assign("register_enabled", $config->get("register_enabled"));
$this->layouts->assign("forgot_password_enabled", $config->get("forgot_password_enabled"));
$this->layouts->assign("auth_url", $this->plugin->getAuthenticationURL());
}
}