Authentication/view/view.html.authentication.php

414 lines
15 KiB
PHP
Executable File

<?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());
}
}