1.3.0
This commit is contained in:
1 parent
1737a2f592
commit
25d47f7528
32 files changed
+620
-108
No files matched your search
@@ -3,6 +3,7 @@ Options -Indexes
|
||||
|
||||
DirectorySlash Off
|
||||
|
||||
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
|
||||
RewriteEngine On
|
||||
RewriteRule ^api(/.*)?$ /index.php?api=$1 [L,QSA]
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
define('WEBROOT', realpath("."));
|
||||
|
||||
include_once 'core/core.php';
|
||||
require_once 'core/datetime.php';
|
||||
include_once 'core/constants.php';
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Api {
|
||||
|
||||
abstract class ApiKeyAPI extends Request {
|
||||
|
||||
protected function apiKeyExists($id) {
|
||||
protected function apiKeyExists($id): bool {
|
||||
$sql = $this->user->getSQL();
|
||||
$res = $sql->select($sql->count())
|
||||
->from("ApiKey")
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace Api {
|
||||
|
||||
abstract class PermissionAPI extends Request {
|
||||
protected function checkStaticPermission() {
|
||||
protected function checkStaticPermission(): bool {
|
||||
if (!$this->user->isLoggedIn() || !$this->user->hasGroup(USER_GROUP_ADMIN)) {
|
||||
return $this->createError("Permission denied.");
|
||||
}
|
||||
@@ -21,6 +21,7 @@ namespace Api\Permission {
|
||||
use Driver\SQL\Column\Column;
|
||||
use Driver\SQL\Condition\Compare;
|
||||
use Driver\SQL\Condition\CondIn;
|
||||
use Driver\SQL\Condition\CondLike;
|
||||
use Driver\SQL\Condition\CondNot;
|
||||
use Driver\SQL\Strategy\UpdateStrategy;
|
||||
use Objects\User;
|
||||
@@ -44,14 +45,14 @@ namespace Api\Permission {
|
||||
$sql = $this->user->getSQL();
|
||||
$res = $sql->select("groups")
|
||||
->from("ApiPermission")
|
||||
->where(new Compare("method", $method))
|
||||
->where(new CondLike($method, new Column("method")))
|
||||
->execute();
|
||||
|
||||
$this->success = ($res !== FALSE);
|
||||
$this->lastError = $sql->getLastError();
|
||||
|
||||
if ($this->success) {
|
||||
if (empty($res)) {
|
||||
if (empty($res) || !is_array($res)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,9 +45,13 @@ class Request {
|
||||
}
|
||||
}
|
||||
|
||||
public function parseParams($values): bool {
|
||||
public function parseParams($values, $structure = NULL): bool {
|
||||
|
||||
foreach ($this->params as $name => $param) {
|
||||
if ($structure === NULL) {
|
||||
$structure = $this->params;
|
||||
}
|
||||
|
||||
foreach ($structure as $name => $param) {
|
||||
$value = $values[$name] ?? NULL;
|
||||
|
||||
$isEmpty = (is_string($value) && strlen($value) === 0) || (is_array($value) && empty($value));
|
||||
@@ -90,7 +94,7 @@ class Request {
|
||||
$values = $_REQUEST;
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_SERVER["CONTENT_TYPE"]) && in_array("application/json", explode(";", $_SERVER["CONTENT_TYPE"]))) {
|
||||
$jsonData = json_decode(file_get_contents('php://input'), true);
|
||||
if ($jsonData) {
|
||||
if ($jsonData !== null) {
|
||||
$values = array_merge($values, $jsonData);
|
||||
} else {
|
||||
$this->lastError = 'Invalid request body.';
|
||||
@@ -124,10 +128,13 @@ class Request {
|
||||
|
||||
// Logged in or api key authorized?
|
||||
if ($this->loginRequired) {
|
||||
if (isset($values['api_key']) && $this->apiKeyAllowed) {
|
||||
$apiKey = $values['api_key'];
|
||||
if (isset($_SERVER["HTTP_AUTHORIZATION"]) && $this->apiKeyAllowed) {
|
||||
$authHeader = $_SERVER["HTTP_AUTHORIZATION"];
|
||||
if (startsWith($authHeader, "Bearer ")) {
|
||||
$apiKey = substr($authHeader, strlen("Bearer "));
|
||||
$apiKeyAuthorized = $this->user->authorize($apiKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->user->isLoggedIn() && !$apiKeyAuthorized) {
|
||||
$this->lastError = 'You are not logged in.';
|
||||
@@ -182,9 +189,13 @@ class Request {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getParam($name) {
|
||||
protected function getParam($name, $obj = NULL) {
|
||||
// i don't know why phpstorm
|
||||
return (isset($this->params[$name]) ? $this->params[$name]->value : NULL);
|
||||
if ($obj === NULL) {
|
||||
$obj = $this->params;
|
||||
}
|
||||
|
||||
return (isset($obj[$name]) ? $obj[$name]->value : NULL);
|
||||
}
|
||||
|
||||
public function isPublic(): bool {
|
||||
@@ -222,4 +233,16 @@ class Request {
|
||||
$this->result['msg'] = $this->lastError;
|
||||
return json_encode($this->result);
|
||||
}
|
||||
|
||||
protected function disableOutputBuffer() {
|
||||
header('X-Accel-Buffering: no');
|
||||
header("Cache-Control: no-transform, no-store, max-age=0");
|
||||
|
||||
ob_implicit_flush(true);
|
||||
$levels = ob_get_level();
|
||||
for ( $i = 0; $i < $levels; $i ++ ) {
|
||||
ob_end_flush();
|
||||
}
|
||||
flush();
|
||||
}
|
||||
}
|
||||
@@ -138,6 +138,7 @@ namespace Api\Routes {
|
||||
->from("Route")
|
||||
->where(new CondBool("active"))
|
||||
->where(new CondRegex($request, new Column("request")))
|
||||
->orderBy("uid")->ascending()
|
||||
->limit(1)
|
||||
->execute();
|
||||
|
||||
|
||||
+194
-36
@@ -6,7 +6,7 @@ namespace Api {
|
||||
|
||||
abstract class UserAPI extends Request {
|
||||
|
||||
protected function userExists(?string $username, ?string $email) {
|
||||
protected function userExists(?string $username, ?string $email = null) {
|
||||
|
||||
$conditions = array();
|
||||
if ($username) {
|
||||
@@ -52,12 +52,19 @@ namespace Api {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function checkRequirements($username, $password, $confirmPassword) {
|
||||
protected function checkUsernameRequirements($username): bool {
|
||||
if (strlen($username) < 5 || strlen($username) > 32) {
|
||||
return $this->createError("The username should be between 5 and 32 characters long");
|
||||
} else if (!preg_match("/[a-zA-Z0-9_\-]+/", $username)) {
|
||||
return $this->createError("The username should only contain the following characters: a-z A-Z 0-9 _ -");
|
||||
}
|
||||
|
||||
return $this->checkPasswordRequirements($password, $confirmPassword);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function checkRequirements($username, $password, $confirmPassword): bool {
|
||||
return $this->checkUsernameRequirements($username) &&
|
||||
$this->checkPasswordRequirements($password, $confirmPassword);
|
||||
}
|
||||
|
||||
protected function insertUser($username, $email, $password, $confirmed) {
|
||||
@@ -123,6 +130,18 @@ namespace Api {
|
||||
->where(new Compare("token", $token))
|
||||
->execute();
|
||||
}
|
||||
|
||||
protected function insertToken(int $userId, string $token, string $tokenType, int $duration): bool {
|
||||
$validUntil = (new \DateTime())->modify("+$duration hour");
|
||||
$sql = $this->user->getSQL();
|
||||
$res = $sql->insert("UserToken", array("user_id", "token", "token_type", "valid_until"))
|
||||
->addRow($userId, $token, $tokenType, $validUntil)
|
||||
->execute();
|
||||
|
||||
$this->success = ($res !== FALSE);
|
||||
$this->lastError = $sql->getLastError();
|
||||
return $this->success;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -193,7 +212,7 @@ namespace Api\User {
|
||||
));
|
||||
}
|
||||
|
||||
private function getUserCount() {
|
||||
private function getUserCount(): bool {
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
$res = $sql->select($sql->count())->from("User")->execute();
|
||||
@@ -359,6 +378,23 @@ namespace Api\User {
|
||||
$this->result["loggedIn"] = false;
|
||||
} else {
|
||||
$this->result["loggedIn"] = true;
|
||||
$userGroups = array_keys($this->user->getGroups());
|
||||
$sql = $this->user->getSQL();
|
||||
$res = $sql->select("method", "groups")
|
||||
->from("ApiPermission")
|
||||
->execute();
|
||||
|
||||
$permissions = [];
|
||||
if (is_array($res)) {
|
||||
foreach ($res as $row) {
|
||||
$requiredGroups = json_decode($row["groups"], true);
|
||||
if (empty($requiredGroups) || !empty(array_intersect($requiredGroups, $userGroups))) {
|
||||
$permissions[] = $row["method"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->result["permissions"] = $permissions;
|
||||
}
|
||||
|
||||
$this->result["user"] = $this->user->jsonSerialize();
|
||||
@@ -456,7 +492,7 @@ namespace Api\User {
|
||||
$this->csrfTokenRequired = false;
|
||||
}
|
||||
|
||||
private function updateUser($uid, $password) {
|
||||
private function updateUser($uid, $password): bool {
|
||||
$sql = $this->user->getSQL();
|
||||
$res = $sql->update("User")
|
||||
->set("password", $this->hashPassword($password))
|
||||
@@ -500,7 +536,6 @@ namespace Api\User {
|
||||
} else if (!$this->updateUser($result["user"]["uid"], $password)) {
|
||||
return false;
|
||||
} else {
|
||||
|
||||
// Invalidate token
|
||||
$this->user->getSQL()
|
||||
->update("UserToken")
|
||||
@@ -519,9 +554,10 @@ namespace Api\User {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
'token' => new StringType('token', 36)
|
||||
));
|
||||
$this->csrfTokenRequired = false;
|
||||
}
|
||||
|
||||
private function updateUser($uid) {
|
||||
private function updateUser($uid): bool {
|
||||
$sql = $this->user->getSQL();
|
||||
$res = $sql->update("User")
|
||||
->set("confirmed", true)
|
||||
@@ -543,7 +579,6 @@ namespace Api\User {
|
||||
}
|
||||
|
||||
$token = $this->getParam("token");
|
||||
|
||||
$req = new CheckToken($this->user);
|
||||
$this->success = $req->execute(array("token" => $token));
|
||||
$this->lastError = $req->getLastError();
|
||||
@@ -579,7 +614,7 @@ namespace Api\User {
|
||||
$this->forbidMethod("GET");
|
||||
}
|
||||
|
||||
private function wrongCredentials() {
|
||||
private function wrongCredentials(): bool {
|
||||
$runtime = microtime(true) - $this->startedAt;
|
||||
$sleepTime = round(3e6 - $runtime);
|
||||
if ($sleepTime > 0) usleep($sleepTime);
|
||||
@@ -613,7 +648,7 @@ namespace Api\User {
|
||||
$this->lastError = $sql->getLastError();
|
||||
|
||||
if ($this->success) {
|
||||
if (count($res) === 0) {
|
||||
if (!is_array($res) || count($res) === 0) {
|
||||
return $this->wrongCredentials();
|
||||
} else {
|
||||
$row = $res[0];
|
||||
@@ -621,6 +656,7 @@ namespace Api\User {
|
||||
$confirmed = $sql->parseBool($row["confirmed"]);
|
||||
if (password_verify($password, $row['password'])) {
|
||||
if (!$confirmed) {
|
||||
$this->result["emailConfirmed"] = false;
|
||||
return $this->createError("Your email address has not been confirmed yet.");
|
||||
} else if (!($this->success = $this->user->createSession($uid, $stayLoggedIn))) {
|
||||
return $this->createError("Error creating Session: " . $sql->getLastError());
|
||||
@@ -681,18 +717,6 @@ namespace Api\User {
|
||||
$this->csrfTokenRequired = false;
|
||||
}
|
||||
|
||||
private function insertToken() {
|
||||
$validUntil = (new DateTime())->modify("+48 hour");
|
||||
$sql = $this->user->getSQL();
|
||||
$res = $sql->insert("UserToken", array("user_id", "token", "token_type", "valid_until"))
|
||||
->addRow($this->userId, $this->token, "email_confirm", $validUntil)
|
||||
->execute();
|
||||
|
||||
$this->success = ($res !== FALSE);
|
||||
$this->lastError = $sql->getLastError();
|
||||
return $this->success;
|
||||
}
|
||||
|
||||
public function execute($values = array()): bool {
|
||||
if (!parent::execute($values)) {
|
||||
return false;
|
||||
@@ -720,6 +744,7 @@ namespace Api\User {
|
||||
$email = $this->getParam('email');
|
||||
$password = $this->getParam("password");
|
||||
$confirmPassword = $this->getParam("confirmPassword");
|
||||
|
||||
if (!$this->userExists($username, $email)) {
|
||||
return false;
|
||||
}
|
||||
@@ -733,14 +758,13 @@ namespace Api\User {
|
||||
return false;
|
||||
}
|
||||
|
||||
$id = $this->insertUser($username, $email, $password, false);
|
||||
if ($id === FALSE) {
|
||||
$this->userId = $this->insertUser($username, $email, $password, false);
|
||||
if (!$this->success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->userId = $id;
|
||||
$this->token = generateRandomString(36);
|
||||
if ($this->insertToken()) {
|
||||
if ($this->insertToken($this->userId, $this->token, "email_confirm", 48)) {
|
||||
$settings = $this->user->getConfiguration()->getSettings();
|
||||
$baseUrl = htmlspecialchars($settings->getBaseUrl());
|
||||
$siteName = htmlspecialchars($settings->getSiteName());
|
||||
@@ -845,6 +869,7 @@ namespace Api\User {
|
||||
));
|
||||
|
||||
$this->loginRequired = true;
|
||||
$this->forbidMethod("GET");
|
||||
}
|
||||
|
||||
public function execute($values = array()): bool {
|
||||
@@ -887,8 +912,8 @@ namespace Api\User {
|
||||
}
|
||||
|
||||
// Check for duplicate username, email
|
||||
$usernameChanged = !is_null($username) ? strcasecmp($username, $user[0]["name"]) !== 0 : false;
|
||||
$emailChanged = !is_null($email) ? strcasecmp($email, $user[0]["email"]) !== 0 : false;
|
||||
$usernameChanged = !is_null($username) && strcasecmp($username, $user[0]["name"]) !== 0;
|
||||
$emailChanged = !is_null($email) && strcasecmp($email, $user[0]["email"]) !== 0;
|
||||
if($usernameChanged || $emailChanged) {
|
||||
if (!$this->userExists($usernameChanged ? $username : NULL, $emailChanged ? $email : NULL)) {
|
||||
return false;
|
||||
@@ -917,7 +942,7 @@ namespace Api\User {
|
||||
$this->success = ($res !== FALSE);
|
||||
}
|
||||
|
||||
if ($this->success && !empty($groupIds)) {
|
||||
if ($this->success) {
|
||||
|
||||
$deleteQuery = $sql->delete("UserGroup")->where(new Compare("user_id", $id));
|
||||
$insertQuery = $sql->insert("UserGroup", array("user_id", "group_id"));
|
||||
@@ -926,7 +951,7 @@ namespace Api\User {
|
||||
$insertQuery->addRow($id, $groupId);
|
||||
}
|
||||
|
||||
$this->success = ($deleteQuery->execute() !== FALSE) && ($insertQuery->execute() !== FALSE);
|
||||
$this->success = ($deleteQuery->execute() !== FALSE) && (empty($groupIds) || $insertQuery->execute() !== FALSE);
|
||||
$this->lastError = $sql->getLastError();
|
||||
}
|
||||
}
|
||||
@@ -983,7 +1008,6 @@ namespace Api\User {
|
||||
}
|
||||
|
||||
parent::__construct($user, $externalCall, $parameters);
|
||||
$this->csrfTokenRequired = false;
|
||||
}
|
||||
|
||||
public function execute($values = array()): bool {
|
||||
@@ -1017,7 +1041,7 @@ namespace Api\User {
|
||||
|
||||
if ($user !== null) {
|
||||
$token = generateRandomString(36);
|
||||
if (!$this->insertToken($user["uid"], $token)) {
|
||||
if (!$this->insertToken($user["uid"], $token, "password_reset", 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1067,16 +1091,102 @@ namespace Api\User {
|
||||
|
||||
return $this->success;
|
||||
}
|
||||
}
|
||||
|
||||
private function insertToken(int $id, string $token) {
|
||||
$validUntil = (new DateTime())->modify("+1 hour");
|
||||
class ResendConfirmEmail extends UserAPI {
|
||||
public function __construct(User $user, $externalCall = false) {
|
||||
$parameters = array(
|
||||
'email' => new Parameter('email', Parameter::TYPE_EMAIL),
|
||||
);
|
||||
|
||||
$settings = $user->getConfiguration()->getSettings();
|
||||
if ($settings->isRecaptchaEnabled()) {
|
||||
$parameters["captcha"] = new StringType("captcha");
|
||||
}
|
||||
|
||||
parent::__construct($user, $externalCall, $parameters);
|
||||
}
|
||||
|
||||
public function execute($values = array()): bool {
|
||||
if (!parent::execute($values)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->user->isLoggedIn()) {
|
||||
return $this->createError("You already logged in.");
|
||||
}
|
||||
|
||||
$settings = $this->user->getConfiguration()->getSettings();
|
||||
if ($settings->isRecaptchaEnabled()) {
|
||||
$captcha = $this->getParam("captcha");
|
||||
$req = new VerifyCaptcha($this->user);
|
||||
if (!$req->execute(array("captcha" => $captcha, "action" => "resendConfirmation"))) {
|
||||
return $this->createError($req->getLastError());
|
||||
}
|
||||
}
|
||||
|
||||
$messageBody = $this->getMessageTemplate("message_confirm_email");
|
||||
if ($messageBody === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$email = $this->getParam("email");
|
||||
$sql = $this->user->getSQL();
|
||||
$res = $sql->insert("UserToken", array("user_id", "token", "token_type", "valid_until"))
|
||||
->addRow($id, $token, "password_reset", $validUntil)
|
||||
$res = $sql->select("User.uid", "User.name", "UserToken.token", "UserToken.token_type", "UserToken.used")
|
||||
->from("User")
|
||||
->leftJoin("UserToken", "User.uid", "UserToken.user_id")
|
||||
->where(new Compare("User.email", $email))
|
||||
->where(new Compare("User.confirmed", false))
|
||||
->execute();
|
||||
|
||||
$this->success = ($res !== FALSE);
|
||||
$this->lastError = $sql->getLastError();
|
||||
if (!$this->success) {
|
||||
return $this->createError($sql->getLastError());
|
||||
} else if (!is_array($res) || empty($res)) {
|
||||
// user does not exist
|
||||
return true;
|
||||
}
|
||||
|
||||
$userId = $res[0]["uid"];
|
||||
$token = current(
|
||||
array_map(function ($row) {
|
||||
return $row["token"];
|
||||
}, array_filter($res, function ($row) use ($sql) {
|
||||
return !$sql->parseBool($row["used"]) && $row["token_type"] === "email_confirm";
|
||||
}))
|
||||
);
|
||||
|
||||
if (!$token) {
|
||||
// no token generated yet, let's generate one
|
||||
$token = generateRandomString(36);
|
||||
if (!$this->insertToken($userId, $token, "email_confirm", 48)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$username = $res[0]["name"];
|
||||
$baseUrl = htmlspecialchars($settings->getBaseUrl());
|
||||
$siteName = htmlspecialchars($settings->getSiteName());
|
||||
$replacements = array(
|
||||
"link" => "$baseUrl/confirmEmail?token=$token",
|
||||
"site_name" => $siteName,
|
||||
"base_url" => $baseUrl,
|
||||
"username" => htmlspecialchars($username)
|
||||
);
|
||||
|
||||
foreach($replacements as $key => $value) {
|
||||
$messageBody = str_replace("{{{$key}}}", $value, $messageBody);
|
||||
}
|
||||
|
||||
$request = new \Api\Mail\Send($this->user);
|
||||
$this->success = $request->execute(array(
|
||||
"to" => $email,
|
||||
"subject" => "[$siteName] E-Mail Confirmation",
|
||||
"body" => $messageBody
|
||||
));
|
||||
|
||||
$this->lastError = $request->getLastError();
|
||||
return $this->success;
|
||||
}
|
||||
}
|
||||
@@ -1138,4 +1248,52 @@ namespace Api\User {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UpdateProfile extends UserAPI {
|
||||
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
'username' => new StringType('username', 32, true, NULL),
|
||||
'password' => new StringType('password', -1, true, NULL),
|
||||
));
|
||||
$this->loginRequired = true;
|
||||
$this->csrfTokenRequired = true;
|
||||
$this->forbidMethod("GET");
|
||||
}
|
||||
|
||||
public function execute($values = array()): bool {
|
||||
if (!parent::execute($values)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$newUsername = $this->getParam("username");
|
||||
$newPassword = $this->getParam("password");
|
||||
|
||||
if ($newUsername === null && $newPassword === null) {
|
||||
return $this->createError("You must either provide an updated username or password");
|
||||
}
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
$query = $sql->update("User")->where(new Compare("id", $this->user->getId()));
|
||||
if ($newUsername !== null) {
|
||||
if (!$this->checkUsernameRequirements($newUsername) || $this->userExists($newUsername)) {
|
||||
return false;
|
||||
} else {
|
||||
$query->set("name", $newUsername);
|
||||
}
|
||||
}
|
||||
|
||||
if ($newPassword !== null) { // TODO: confirm password?
|
||||
if (!$this->checkPasswordRequirements($newPassword, $newPassword)) {
|
||||
return false;
|
||||
} else {
|
||||
$query->set("password", $this->hashPassword($newPassword));
|
||||
}
|
||||
}
|
||||
|
||||
$this->success = $query->execute();
|
||||
$this->lastError = $sql->getLastError();
|
||||
return $this->success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,10 +131,11 @@ class CreateDatabase extends DatabaseScript {
|
||||
|
||||
$queries[] = $sql->insert("Route", array("request", "action", "target", "extra"))
|
||||
->addRow("^/admin(/.*)?$", "dynamic", "\\Documents\\Admin", NULL)
|
||||
->addRow("^/register(/)?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\Register")
|
||||
->addRow("^/confirmEmail(/)?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\ConfirmEmail")
|
||||
->addRow("^/acceptInvite(/)?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\AcceptInvite")
|
||||
->addRow("^/resetPassword(/)?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\ResetPassword")
|
||||
->addRow("^/register/?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\Register")
|
||||
->addRow("^/confirmEmail/?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\ConfirmEmail")
|
||||
->addRow("^/acceptInvite/?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\AcceptInvite")
|
||||
->addRow("^/resetPassword/?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\ResetPassword")
|
||||
->addRow("^/resendConfirmEmail/?$", "dynamic", "\\Documents\\Account", "\\Views\\Account\\ResendConfirmEmail")
|
||||
->addRow("^/$", "static", "/static/welcome.html", NULL);
|
||||
|
||||
$queries[] = $sql->createTable("Settings")
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace Documents {
|
||||
namespace Documents\Account {
|
||||
|
||||
use Elements\Head;
|
||||
use Elements\Link;
|
||||
use Elements\Script;
|
||||
use Elements\SimpleBody;
|
||||
|
||||
@@ -32,6 +33,7 @@ namespace Documents\Account {
|
||||
$this->addJS(Script::ACCOUNT);
|
||||
$this->loadBootstrap();
|
||||
$this->loadFontawesome();
|
||||
$this->addCSS(Link::CORE);
|
||||
}
|
||||
|
||||
protected function initMetas(): array {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Driver\SQL\Condition;
|
||||
|
||||
|
||||
use Driver\SQL\Query\Select;
|
||||
|
||||
class Exists extends Condition
|
||||
{
|
||||
private Select $subQuery;
|
||||
|
||||
public function __construct(Select $subQuery)
|
||||
{
|
||||
$this->subQuery = $subQuery;
|
||||
}
|
||||
|
||||
public function getSubQuery(): Select
|
||||
{
|
||||
return $this->subQuery;
|
||||
}
|
||||
}
|
||||
@@ -340,7 +340,7 @@ class PostgreSQL extends SQL {
|
||||
return ($statusTexts[$status] ?? "Unknown") . " (v$version)";
|
||||
}
|
||||
|
||||
protected function buildCondition($condition, &$params) {
|
||||
public function buildCondition($condition, &$params) {
|
||||
if($condition instanceof CondRegex) {
|
||||
$left = $condition->getLeftExp();
|
||||
$right = $condition->getRightExp();
|
||||
|
||||
@@ -54,6 +54,11 @@ class AlterTable extends Query {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function resetAutoIncrement(): AlterTable {
|
||||
$this->action = "RESET_AUTO_INCREMENT";
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAction(): string { return $this->action; }
|
||||
public function getColumn(): ?Column { return $this->column; }
|
||||
public function getConstraint(): ?Constraint { return $this->constraint; }
|
||||
@@ -65,6 +70,10 @@ class AlterTable extends Query {
|
||||
$column = $this->getColumn();
|
||||
$constraint = $this->getConstraint();
|
||||
|
||||
if ($action === "RESET_AUTO_INCREMENT") {
|
||||
return "ALTER TABLE $tableName AUTO_INCREMENT=1";
|
||||
}
|
||||
|
||||
$query = "ALTER TABLE $tableName $action ";
|
||||
|
||||
if ($column) {
|
||||
|
||||
@@ -23,6 +23,7 @@ class Select extends Query {
|
||||
$this->selectValues = (!empty($selectValues) && is_array($selectValues[0])) ? $selectValues[0] : $selectValues;
|
||||
$this->tables = array();
|
||||
$this->conditions = array();
|
||||
$this->havings = array();
|
||||
$this->joins = array();
|
||||
$this->orderColumns = array();
|
||||
$this->groupColumns = array();
|
||||
@@ -41,6 +42,11 @@ class Select extends Query {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function having(...$conditions): Select {
|
||||
$this->havings[] = (count($conditions) === 1 ? $conditions : new CondOr($conditions));
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function innerJoin(string $table, string $columnA, string $columnB, ?string $tableAlias = null): Select {
|
||||
$this->joins[] = new Join("INNER", $table, $columnA, $columnB, $tableAlias);
|
||||
return $this;
|
||||
@@ -94,6 +100,7 @@ class Select extends Query {
|
||||
public function getLimit(): int { return $this->limit; }
|
||||
public function getOffset(): int { return $this->offset; }
|
||||
public function getGroupBy(): array { return $this->groupColumns; }
|
||||
public function getHavings(): array { return $this->havings; }
|
||||
|
||||
public function build(array &$params): ?string {
|
||||
|
||||
@@ -101,6 +108,17 @@ class Select extends Query {
|
||||
foreach ($this->selectValues as $value) {
|
||||
if (is_string($value)) {
|
||||
$selectValues[] = $this->sql->columnName($value);
|
||||
} else if ($value instanceof Select) {
|
||||
$subSelect = $value->build($params);
|
||||
if (count($value->getSelectValues()) !== 1) {
|
||||
$selectValues[] = "($subSelect)";
|
||||
} else {
|
||||
$columnName = $value->getSelectValues()[0];
|
||||
if(($index = stripos($columnName, " as ")) !== FALSE) {
|
||||
$columnName = substr($columnName, $index + 4);
|
||||
}
|
||||
$selectValues[] = "($subSelect) as $columnName";
|
||||
}
|
||||
} else {
|
||||
$selectValues[] = $this->sql->addValue($value, $params);
|
||||
}
|
||||
@@ -115,6 +133,10 @@ class Select extends Query {
|
||||
|
||||
$tables = $this->sql->tableName($tables);
|
||||
$where = $this->sql->getWhereClause($this->getConditions(), $params);
|
||||
$havingClause = "";
|
||||
if (count($this->havings) > 0) {
|
||||
$havingClause = " HAVING " . $this->sql->buildCondition($this->getHavings(), $params);
|
||||
}
|
||||
|
||||
$joinStr = "";
|
||||
$joins = $this->getJoins();
|
||||
@@ -145,6 +167,6 @@ class Select extends Query {
|
||||
|
||||
$limit = ($this->getLimit() > 0 ? (" LIMIT " . $this->getLimit()) : "");
|
||||
$offset = ($this->getOffset() > 0 ? (" OFFSET " . $this->getOffset()) : "");
|
||||
return "SELECT $selectValues FROM $tables$joinStr$where$groupBy$orderBy$limit$offset";
|
||||
return "SELECT $selectValues FROM $tables$joinStr$where$groupBy$havingClause$orderBy$limit$offset";
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace Driver\SQL;
|
||||
|
||||
use Driver\SQL\Column\Column;
|
||||
use Driver\SQL\Condition\Compare;
|
||||
use Driver\SQL\Condition\CondAnd;
|
||||
use Driver\SQL\Condition\CondBool;
|
||||
use Driver\SQL\Condition\CondIn;
|
||||
use Driver\SQL\Condition\Condition;
|
||||
@@ -11,6 +12,7 @@ use Driver\SQL\Condition\CondKeyword;
|
||||
use Driver\SQL\Condition\CondNot;
|
||||
use Driver\Sql\Condition\CondNull;
|
||||
use Driver\SQL\Condition\CondOr;
|
||||
use Driver\SQL\Condition\Exists;
|
||||
use Driver\SQL\Constraint\Constraint;
|
||||
use \Driver\SQL\Constraint\Unique;
|
||||
use \Driver\SQL\Constraint\PrimaryKey;
|
||||
@@ -234,7 +236,7 @@ abstract class SQL {
|
||||
// Statements
|
||||
protected abstract function execute($query, $values=NULL, $returnValues=false);
|
||||
|
||||
protected function buildCondition($condition, &$params) {
|
||||
public function buildCondition($condition, &$params) {
|
||||
|
||||
if ($condition instanceof CondOr) {
|
||||
$conditions = array();
|
||||
@@ -242,6 +244,12 @@ abstract class SQL {
|
||||
$conditions[] = $this->buildCondition($cond, $params);
|
||||
}
|
||||
return "(" . implode(" OR ", $conditions) . ")";
|
||||
} else if ($condition instanceof CondAnd) {
|
||||
$conditions = array();
|
||||
foreach($condition->getConditions() as $cond) {
|
||||
$conditions[] = $this->buildCondition($cond, $params);
|
||||
}
|
||||
return "(" . implode(" AND ", $conditions) . ")";
|
||||
} else if ($condition instanceof Compare) {
|
||||
$column = $this->columnName($condition->getColumn());
|
||||
$value = $condition->getValue();
|
||||
@@ -304,6 +312,8 @@ abstract class SQL {
|
||||
return "NOT $expression";
|
||||
} else if ($condition instanceof CondNull) {
|
||||
return $this->columnName($condition->getColumn()) . " IS NULL";
|
||||
} else if ($condition instanceof Exists) {
|
||||
return "EXISTS(" .$condition->getSubQuery()->build($params) . ")";
|
||||
} else {
|
||||
$this->lastError = "Unsupported condition type: " . get_class($condition);
|
||||
return null;
|
||||
|
||||
@@ -11,7 +11,8 @@ class Script extends StaticView {
|
||||
const INSTALL = "/js/install.js";
|
||||
const BOOTSTRAP = "/js/bootstrap.bundle.min.js";
|
||||
const ACCOUNT = "/js/account.js";
|
||||
const FILES = "/js/files.min.js";
|
||||
const SECLAB = "/js/seclab.min.js";
|
||||
const FONTAWESOME = "/js/fontawesome-all.min.js";
|
||||
|
||||
private string $type;
|
||||
private string $content;
|
||||
|
||||
Vendored
+7
@@ -68,6 +68,13 @@ namespace External\ZipStream {
|
||||
$this->fileHandle = fopen($filename, 'rb');
|
||||
}
|
||||
|
||||
public function loadFromBuffer($buf) {
|
||||
$this->crc32 = hash('crc32b', $buf, true);
|
||||
$this->sha256 = hash('sha256', $buf);
|
||||
$this->fileSize = strlen($buf);
|
||||
$this->content = $buf;
|
||||
}
|
||||
|
||||
public function name() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace Objects;
|
||||
|
||||
class AesStream {
|
||||
|
||||
private string $key;
|
||||
private string $iv;
|
||||
private $callback;
|
||||
private ?string $outputFile;
|
||||
private ?string $inputFile;
|
||||
|
||||
public function __construct(string $key, string $iv) {
|
||||
$this->key = $key;
|
||||
$this->iv = $iv;
|
||||
$this->inputFile = null;
|
||||
$this->outputFile = null;
|
||||
$this->callback = null;
|
||||
|
||||
if (!in_array(strlen($key), [16, 24, 32])) {
|
||||
throw new \Exception("Invalid Key Size");
|
||||
} else if (strlen($iv) !== 16) {
|
||||
throw new \Exception("Invalid IV Size");
|
||||
}
|
||||
}
|
||||
|
||||
public function setInput($file) {
|
||||
$this->inputFile = $file;
|
||||
}
|
||||
|
||||
public function setOutput($callback) {
|
||||
$this->callback = $callback;
|
||||
}
|
||||
|
||||
public function setOutputFile(string $file) {
|
||||
$this->outputFile = $file;
|
||||
}
|
||||
|
||||
private function add(string $a, int $b): string {
|
||||
// counter $b is n = PHP_INT_SIZE bytes large
|
||||
$b_arr = pack('I', $b);
|
||||
$b_size = strlen($b_arr);
|
||||
$a_size = strlen($a);
|
||||
|
||||
$prefix = "";
|
||||
if ($a_size > $b_size) {
|
||||
$prefix = substr($a, 0, $a_size - $b_size);
|
||||
}
|
||||
|
||||
// xor last n bytes of $a with $b
|
||||
$xor = substr($a, strlen($prefix), $b_size);
|
||||
if (strlen($xor) !== strlen($b_arr)) {
|
||||
var_dump($xor);
|
||||
var_dump($b_arr);
|
||||
die();
|
||||
}
|
||||
$xor = $this->xor($xor, $b_arr);
|
||||
return $prefix . $xor;
|
||||
}
|
||||
|
||||
private function xor(string $a, string $b): string {
|
||||
$arr_a = str_split($a);
|
||||
$arr_b = str_split($b);
|
||||
if (strlen($a) !== strlen($b)) {
|
||||
var_dump($a);
|
||||
var_dump($b);
|
||||
var_dump(range(0, strlen($a) - 1));
|
||||
die();
|
||||
}
|
||||
|
||||
return implode("", array_map(function($i) use ($arr_a, $arr_b) {
|
||||
return chr(ord($arr_a[$i]) ^ ord($arr_b[$i]));
|
||||
}, range(0, strlen($a) - 1)));
|
||||
}
|
||||
|
||||
public function start(): bool {
|
||||
if (!$this->inputFile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$blockSize = 16;
|
||||
$bitStrength = strlen($this->key) * 8;
|
||||
$aesMode = "AES-$bitStrength-ECB";
|
||||
|
||||
$outputHandle = null;
|
||||
$inputHandle = fopen($this->inputFile, "rb");
|
||||
if (!$inputHandle) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->outputFile !== null) {
|
||||
$outputHandle = fopen($this->outputFile, "wb");
|
||||
if (!$outputHandle) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$counter = 0;
|
||||
while (!feof($inputHandle)) {
|
||||
$chunk = fread($inputHandle, 4096);
|
||||
$chunkSize = strlen($chunk);
|
||||
for ($offset = 0; $offset < $chunkSize; $offset += $blockSize) {
|
||||
$block = substr($chunk, $offset, $blockSize);
|
||||
if (strlen($block) !== $blockSize) {
|
||||
$padding = ($blockSize - strlen($block));
|
||||
$block .= str_repeat(chr($padding), $padding);
|
||||
}
|
||||
|
||||
$ivCounter = $this->add($this->iv, $counter + 1);
|
||||
$encrypted = substr(openssl_encrypt($ivCounter, $aesMode, $this->key, OPENSSL_RAW_DATA), 0, $blockSize);
|
||||
$encrypted = $this->xor($encrypted, $block);
|
||||
if (is_callable($this->callback)) {
|
||||
call_user_func($this->callback, $encrypted);
|
||||
}
|
||||
|
||||
if ($outputHandle !== null) {
|
||||
fwrite($outputHandle, $encrypted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fclose($inputHandle);
|
||||
if ($outputHandle) fclose($outputHandle);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ class Session extends ApiObject {
|
||||
$token = array('userId' => $this->user->getId(), 'sessionId' => $this->sessionId);
|
||||
$sessionCookie = JWT::encode($token, $settings->getJwtSecret());
|
||||
$secure = strcmp(getProtocol(), "https") === 0;
|
||||
setcookie('session', $sessionCookie, $this->getExpiresTime(), "/", "", $secure);
|
||||
setcookie('session', $sessionCookie, $this->getExpiresTime(), "/", "", $secure, true);
|
||||
}
|
||||
|
||||
public function getExpiresTime(): int {
|
||||
|
||||
@@ -27,7 +27,7 @@ class User extends ApiObject {
|
||||
$this->connectDb();
|
||||
|
||||
if (!is_cli()) {
|
||||
session_start();
|
||||
@session_start();
|
||||
$this->setLanguage(Language::DEFAULT_LANGUAGE());
|
||||
$this->parseCookies();
|
||||
}
|
||||
@@ -227,9 +227,12 @@ class User extends ApiObject {
|
||||
}
|
||||
|
||||
$res = $this->sql->select("ApiKey.user_id as uid", "User.name", "User.email", "User.confirmed",
|
||||
"Language.uid as langId", "Language.code as langCode", "Language.name as langName")
|
||||
"Language.uid as langId", "Language.code as langCode", "Language.name as langName",
|
||||
"Group.uid as groupId", "Group.name as groupName")
|
||||
->from("ApiKey")
|
||||
->innerJoin("User", "ApiKey.user_id", "User.uid")
|
||||
->leftJoin("UserGroup", "UserGroup.user_id", "User.uid")
|
||||
->leftJoin("Group", "UserGroup.group_id", "Group.uid")
|
||||
->leftJoin("Language", "User.language_id", "Language.uid")
|
||||
->where(new Compare("ApiKey.api_key", $apiKey))
|
||||
->where(new Compare("valid_until", $this->sql->currentTimestamp(), ">"))
|
||||
@@ -253,6 +256,10 @@ class User extends ApiObject {
|
||||
if(!is_null($row['langId'])) {
|
||||
$this->setLanguage(Language::newInstance($row['langId'], $row['langCode'], $row['langName']));
|
||||
}
|
||||
|
||||
foreach($res as $row) {
|
||||
$this->groups[$row["groupId"]] = $row["groupName"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,13 +73,13 @@ class AcceptInvite extends AccountView {
|
||||
<div class=\"input-group-append\">
|
||||
<span class=\"input-group-text\"><i class=\"fas fa-key\"></i></span>
|
||||
</div>
|
||||
<input type=\"password\" name='password' id='password' class=\"form-control\" placeholder=\"Password\">
|
||||
<input type=\"password\" autocomplete='new-password' name='password' id='password' class=\"form-control\" placeholder=\"Password\">
|
||||
</div>
|
||||
<div class=\"input-group mt-3\">
|
||||
<div class=\"input-group-append\">
|
||||
<span class=\"input-group-text\"><i class=\"fas fa-key\"></i></span>
|
||||
</div>
|
||||
<input type=\"password\" name='confirmPassword' id='confirmPassword' class=\"form-control\" placeholder=\"Confirm Password\">
|
||||
<input type=\"password\" autocomplete='new-password' name='confirmPassword' id='confirmPassword' class=\"form-control\" placeholder=\"Confirm Password\">
|
||||
</div>
|
||||
<div class=\"input-group mt-3\">
|
||||
<button type=\"button\" class=\"btn btn-success\" id='btnAcceptInvite'>Submit</button>
|
||||
|
||||
@@ -34,14 +34,14 @@ abstract class AccountView extends View {
|
||||
|
||||
$html .= "<div class=\"container mt-5\">
|
||||
<div class=\"row\">
|
||||
<div class=\"col-md-4 py-5 bg-primary text-white text-center\" style='border-top-left-radius:.4em;border-bottom-left-radius:.4em'>
|
||||
<div class=\"col-md-3 py-5 bg-primary text-white text-center\" style='border-top-left-radius:.4em;border-bottom-left-radius:.4em;margin-left: auto'>
|
||||
<div class=\"card-body\">
|
||||
$icon
|
||||
<h2 class=\"py-3\">$this->title</h2>
|
||||
<p>$this->description</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class=\"col-md-8 pt-5 pb-2 border border-info\" style='border-top-right-radius:.4em;border-bottom-right-radius:.4em'>
|
||||
<div class=\"col-md-5 pt-5 pb-2 border border-info\" style='border-top-right-radius:.4em;border-bottom-right-radius:.4em;margin-right:auto'>
|
||||
$content
|
||||
<div class='alert mt-2' style='display:none' id='alertMessage'></div>
|
||||
</div>
|
||||
|
||||
@@ -5,42 +5,51 @@ namespace Views\Account;
|
||||
|
||||
|
||||
use Elements\Document;
|
||||
use Elements\Script;
|
||||
|
||||
class ConfirmEmail extends AccountView {
|
||||
|
||||
private bool $success;
|
||||
private string $message;
|
||||
|
||||
public function __construct(Document $document, $loadView = true) {
|
||||
parent::__construct($document, $loadView);
|
||||
$this->title = "Confirm Email";
|
||||
$this->description = "Request a password reset, once you got the e-mail address, you can choose a new password";
|
||||
$this->icon = "user-check";
|
||||
$this->success = false;
|
||||
$this->message = "No content";
|
||||
}
|
||||
|
||||
public function loadView() {
|
||||
parent::loadView();
|
||||
|
||||
if (isset($_GET["token"]) && is_string($_GET["token"]) && !empty($_GET["token"])) {
|
||||
$req = new \Api\User\ConfirmEmail($this->getDocument()->getUser());
|
||||
$this->success = $req->execute(array("token" => $_GET["token"]));
|
||||
if ($this->success) {
|
||||
$this->message = "Your e-mail address was successfully confirmed, you may now log in";
|
||||
$this->getDocument()->getHead()->addScript(Script::MIME_TEXT_JAVASCRIPT, "", '
|
||||
$(document).ready(function() {
|
||||
var token = jsCore.getParameter("token");
|
||||
if (token) {
|
||||
jsCore.apiCall("/user/confirmEmail", { token: token }, (res) => {
|
||||
$("#confirm-status").removeClass("alert-info");
|
||||
if (!res.success) {
|
||||
$("#confirm-status").addClass("alert-danger");
|
||||
$("#confirm-status").text("Error confirming e-mail address: " + res.msg);
|
||||
} else {
|
||||
$this->message = "Error confirming e-mail address: " . $req->getLastError();
|
||||
$("#confirm-status").addClass("alert-success");
|
||||
$("#confirm-status").text("Your e-mail address was successfully confirmed, you may now log in.");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$this->success = false;
|
||||
$this->message = "The link you visited is no longer valid";
|
||||
$("#confirm-status").removeClass("alert-info");
|
||||
$("#confirm-status").addClass("alert-danger");
|
||||
$("#confirm-status").text("The link you visited is no longer valid");
|
||||
}
|
||||
});'
|
||||
);
|
||||
}
|
||||
|
||||
protected function getAccountContent() {
|
||||
if ($this->success) {
|
||||
return $this->createSuccessText($this->message);
|
||||
} else {
|
||||
return $this->createErrorText($this->message);
|
||||
}
|
||||
|
||||
$spinner = $this->createIcon("spinner");
|
||||
$html = "<noscript><div class=\"alert alert-danger\">Javascript is required</div></noscript>
|
||||
<div class=\"alert alert-info\" id=\"confirm-status\">
|
||||
Confirming email… $spinner
|
||||
</div>";
|
||||
|
||||
$html .= "<a href='/login'><button class='btn btn-primary' style='position: absolute; bottom: 10px' type='button'>Proceed to Login</button></a>";
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,14 @@ class Register extends AccountView {
|
||||
|
||||
public function getAccountContent() {
|
||||
|
||||
$settings = $this->getDocument()->getUser()->getConfiguration()->getSettings();
|
||||
$user = $this->getDocument()->getUser();
|
||||
if ($user->isLoggedIn()) {
|
||||
header(302);
|
||||
header("Location: /");
|
||||
die("You are already logged in.");
|
||||
}
|
||||
|
||||
$settings = $user->getConfiguration()->getSettings();
|
||||
if (!$settings->isRegistrationAllowed()) {
|
||||
return $this->createErrorText(
|
||||
"Registration is not enabled on this website. If you are an administrator,
|
||||
@@ -30,28 +37,33 @@ class Register extends AccountView {
|
||||
<div class=\"input-group-append\">
|
||||
<span class=\"input-group-text\"><i class=\"fas fa-hashtag\"></i></span>
|
||||
</div>
|
||||
<input id=\"username\" name=\"username\" placeholder=\"Username\" class=\"form-control\" type=\"text\" maxlength=\"32\">
|
||||
<input id=\"username\" autocomplete='username' name=\"username\" placeholder=\"Username\" class=\"form-control\" type=\"text\" maxlength=\"32\">
|
||||
</div>
|
||||
<div class=\"input-group mt-3\">
|
||||
<div class=\"input-group-append\">
|
||||
<span class=\"input-group-text\"><i class=\"fas fa-at\"></i></span>
|
||||
</div>
|
||||
<input type=\"email\" name='email' id='email' class=\"form-control\" placeholder=\"Email\" maxlength=\"64\">
|
||||
<input type=\"email\" autocomplete='email' name='email' id='email' class=\"form-control\" placeholder=\"Email\" maxlength=\"64\">
|
||||
</div>
|
||||
<div class=\"input-group mt-3\">
|
||||
<div class=\"input-group-append\">
|
||||
<span class=\"input-group-text\"><i class=\"fas fa-key\"></i></span>
|
||||
</div>
|
||||
<input type=\"password\" name='password' id='password' class=\"form-control\" placeholder=\"Password\">
|
||||
<input type=\"password\" autocomplete='new-password' name='password' id='password' class=\"form-control\" placeholder=\"Password\">
|
||||
</div>
|
||||
<div class=\"input-group mt-3\">
|
||||
<div class=\"input-group-append\">
|
||||
<span class=\"input-group-text\"><i class=\"fas fa-key\"></i></span>
|
||||
</div>
|
||||
<input type=\"password\" name='confirmPassword' id='confirmPassword' class=\"form-control\" placeholder=\"Confirm Password\">
|
||||
<input type=\"password\" autocomplete='new-password' name='confirmPassword' id='confirmPassword' class=\"form-control\" placeholder=\"Confirm Password\">
|
||||
</div>
|
||||
<div class=\"input-group mt-3\">
|
||||
<button type=\"button\" class=\"btn btn-success\" id='btnRegister'>Submit</button>
|
||||
<button type=\"button\" class=\"btn btn-primary\" id='btnRegister'>Submit</button>
|
||||
<a href='/login' style='margin-left: 10px'>
|
||||
<button class='btn btn-secondary' type='button'>
|
||||
Back to Login
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</form>";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Views\Account;
|
||||
|
||||
|
||||
use Elements\Document;
|
||||
|
||||
class ResendConfirmEmail extends AccountView {
|
||||
|
||||
public function __construct(Document $document, $loadView = true) {
|
||||
parent::__construct($document, $loadView);
|
||||
$this->title = "Resend Confirm Email";
|
||||
$this->description = "Request a new confirmation email to finalize the account creation";
|
||||
$this->icon = "envelope";
|
||||
}
|
||||
|
||||
protected function getAccountContent() {
|
||||
return "<p class='lead'>Enter your E-Mail address, to receive a new e-mail to confirm your registration.</p>
|
||||
<form>
|
||||
<div class=\"input-group\">
|
||||
<div class=\"input-group-append\">
|
||||
<span class=\"input-group-text\"><i class=\"fas fa-at\"></i></span>
|
||||
</div>
|
||||
<input id=\"email\" autocomplete='email' name=\"email\" placeholder=\"E-Mail address\" class=\"form-control\" type=\"email\" maxlength=\"64\" />
|
||||
</div>
|
||||
<div class=\"input-group mt-2\" style='position: absolute;bottom: 15px'>
|
||||
<button id='btnResendConfirmEmail' class='btn btn-primary'>
|
||||
Request
|
||||
</button>
|
||||
<a href='/login' style='margin-left: 10px'>
|
||||
<button class='btn btn-secondary' type='button'>
|
||||
Back to Login
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
";
|
||||
}
|
||||
}
|
||||
@@ -56,10 +56,17 @@ class ResetPassword extends AccountView {
|
||||
<div class=\"input-group-append\">
|
||||
<span class=\"input-group-text\"><i class=\"fas fa-at\"></i></span>
|
||||
</div>
|
||||
<input id=\"email\" name=\"email\" placeholder=\"E-Mail address\" class=\"form-control\" type=\"email\" maxlength=\"64\" />
|
||||
<input id=\"email\" autocomplete='email' name=\"email\" placeholder=\"E-Mail address\" class=\"form-control\" type=\"email\" maxlength=\"64\" />
|
||||
</div>
|
||||
<div class=\"input-group mt-2\">
|
||||
<button id='btnRequestPasswordReset' class='btn btn-primary'>Request</button>
|
||||
<div class=\"input-group mt-2\" style='position: absolute;bottom: 15px'>
|
||||
<button id='btnRequestPasswordReset' class='btn btn-primary'>
|
||||
Request
|
||||
</button>
|
||||
<a href='/login' style='margin-left: 10px'>
|
||||
<button class='btn btn-secondary' type='button'>
|
||||
Back to Login
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
";
|
||||
} else {
|
||||
@@ -70,13 +77,13 @@ class ResetPassword extends AccountView {
|
||||
<div class=\"input-group-append\">
|
||||
<span class=\"input-group-text\"><i class=\"fas fa-key\"></i></span>
|
||||
</div>
|
||||
<input type=\"password\" name='password' id='password' class=\"form-control\" placeholder=\"Password\">
|
||||
<input type=\"password\" autocomplete='new-password' name='password' id='password' class=\"form-control\" placeholder=\"Password\">
|
||||
</div>
|
||||
<div class=\"input-group mt-3\">
|
||||
<div class=\"input-group-append\">
|
||||
<span class=\"input-group-text\"><i class=\"fas fa-key\"></i></span>
|
||||
</div>
|
||||
<input type=\"password\" name='confirmPassword' id='confirmPassword' class=\"form-control\" placeholder=\"Confirm Password\">
|
||||
<input type=\"password\" autocomplete='new-password' name='confirmPassword' id='confirmPassword' class=\"form-control\" placeholder=\"Confirm Password\">
|
||||
</div>
|
||||
<div class=\"input-group mt-3\">
|
||||
<button type=\"button\" class=\"btn btn-success\" id='btnResetPassword'>Submit</button>
|
||||
|
||||
+20
-8
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
define("WEBBASE_VERSION", "1.2.5");
|
||||
define("WEBBASE_VERSION", "1.3.0");
|
||||
|
||||
spl_autoload_extensions(".php");
|
||||
spl_autoload_register(function($class) {
|
||||
$full_path = getClassPath($class);
|
||||
$full_path = WEBROOT . "/" . getClassPath($class);
|
||||
if (file_exists($full_path)) {
|
||||
include_once $full_path;
|
||||
} else {
|
||||
@@ -24,10 +24,24 @@ function getProtocol(): string {
|
||||
return $isSecure ? 'https' : 'http';
|
||||
}
|
||||
|
||||
function generateRandomString($length): string {
|
||||
function generateRandomString($length, $type = "ascii"): string {
|
||||
$randomString = '';
|
||||
|
||||
$lowercase = "abcdefghijklmnopqrstuvwxyz";
|
||||
$uppercase = strtoupper($lowercase);
|
||||
$digits = "0123456789";
|
||||
$hex = $digits . substr($lowercase, 0, 6);
|
||||
$ascii = $lowercase . $uppercase . $digits;
|
||||
|
||||
if ($length > 0) {
|
||||
$numCharacters = 26 + 26 + 10; // a-z + A-Z + 0-9
|
||||
$type = strtolower($type);
|
||||
if ($type === "hex") {
|
||||
$charset = $hex;
|
||||
} else {
|
||||
$charset = $ascii;
|
||||
}
|
||||
|
||||
$numCharacters = strlen($charset);
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
try {
|
||||
$num = random_int(0, $numCharacters - 1);
|
||||
@@ -35,9 +49,7 @@ function generateRandomString($length): string {
|
||||
$num = rand(0, $numCharacters - 1);
|
||||
}
|
||||
|
||||
if ($num < 26) $randomString .= chr(ord('a') + $num);
|
||||
else if ($num - 26 < 26) $randomString .= chr(ord('A') + $num - 26);
|
||||
else $randomString .= chr(ord('0') + $num - 26 - 26);
|
||||
$randomString .= $charset[$num];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +136,7 @@ function urlId($str) {
|
||||
return urlencode(htmlspecialchars(preg_replace("[: ]","-", $str)));
|
||||
}
|
||||
|
||||
function getClassPath($class, $suffix = true) {
|
||||
function getClassPath($class, $suffix = true): string {
|
||||
$path = str_replace('\\', '/', $class);
|
||||
$path = array_values(array_filter(explode("/", $path)));
|
||||
|
||||
|
||||
+1
-2
@@ -95,8 +95,7 @@ function getMonthName($month) {
|
||||
|
||||
function isInPast($d) {
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if(is_a($d, "DateTime")) $d = $d->format('Y-m-d H:i:s');
|
||||
return (strtotime($d) < strtotime($now));
|
||||
return datetimeDiff($d, $now) > 0;
|
||||
}
|
||||
|
||||
function datetimeDiff($d1, $d2) {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
php_flag engine off
|
||||
@@ -0,0 +1 @@
|
||||
php_flag engine on
|
||||
+40
-6
@@ -4,9 +4,13 @@ $(document).ready(function () {
|
||||
return (typeof grecaptcha !== 'undefined');
|
||||
}
|
||||
|
||||
function showAlert(type, msg) {
|
||||
function showAlert(type, msg, raw=false) {
|
||||
let alert = $("#alertMessage");
|
||||
if (raw) {
|
||||
alert.html(msg);
|
||||
} else {
|
||||
alert.text(msg);
|
||||
}
|
||||
alert.attr("class", "mt-2 alert alert-" + type);
|
||||
alert.show();
|
||||
}
|
||||
@@ -51,8 +55,12 @@ $(document).ready(function () {
|
||||
btn.prop("disabled", false);
|
||||
$("#password").val("");
|
||||
createdDiv.hide();
|
||||
if (res.emailConfirmed === false) {
|
||||
showAlert("danger", res.msg + ' <a href="/resendConfirmation">Click here</a> to resend the confirmation mail.', true);
|
||||
} else {
|
||||
showAlert("danger", res.msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -79,14 +87,14 @@ $(document).ready(function () {
|
||||
params["captcha"] = captcha;
|
||||
submitForm(btn, "user/register", params, () => {
|
||||
showAlert("success", "Account successfully created, check your emails.");
|
||||
$("input").val("");
|
||||
$("input:not([id='siteKey'])").val("");
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
submitForm(btn, "user/register", params, () => {
|
||||
showAlert("success", "Account successfully created, check your emails.");
|
||||
$("input").val("");
|
||||
$("input:not([id='siteKey'])").val("");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -137,14 +145,14 @@ $(document).ready(function () {
|
||||
params["captcha"] = captcha;
|
||||
submitForm(btn, "user/requestPasswordReset", params, () => {
|
||||
showAlert("success", "If the e-mail address exists and is linked to a account, you will receive a password reset token.");
|
||||
$("input").val("");
|
||||
$("input:not([id='siteKey'])").val("");
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
submitForm(btn, "user/requestPasswordReset", params, () => {
|
||||
showAlert("success", "If the e-mail address exists and is linked to a account, you will receive a password reset token.");
|
||||
$("input").val("");
|
||||
$("input:not([id='siteKey'])").val("");
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -173,9 +181,35 @@ $(document).ready(function () {
|
||||
showAlert("danger", res.msg);
|
||||
} else {
|
||||
showAlert("success", "Your password was successfully changed. You may now login.");
|
||||
$("input").val("");
|
||||
$("input:not([id='siteKey'])").val("");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$("#btnResendConfirmEmail").click(function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
let btn = $(this);
|
||||
let email = $("#email").val();
|
||||
let params = { email: email };
|
||||
if (isRecaptchaEnabled()) {
|
||||
let siteKey = $("#siteKey").val().trim();
|
||||
grecaptcha.ready(function() {
|
||||
grecaptcha.execute(siteKey, {action: 'resendConfirmation'}).then(function(captcha) {
|
||||
params["captcha"] = captcha;
|
||||
submitForm(btn, "user/resendConfirmEmail", params, () => {
|
||||
showAlert("success", "If the e-mail address exists and is linked to a account, you will receive a new confirmation email.");
|
||||
$("input:not([id='siteKey'])").val("");
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
submitForm(btn, "user/resendConfirmEmail", params, () => {
|
||||
showAlert("success", "\"If the e-mail address exists and is linked to a account, you will receive a new confirmation email.");
|
||||
$("input:not([id='siteKey'])").val("");
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
/*!
|
||||
* Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
*/
|
||||
!function(){"use strict";var c={},l={};try{"undefined"!=typeof window&&(c=window),"undefined"!=typeof document&&(l=document)}catch(c){}var h=(c.navigator||{}).userAgent,a=void 0===h?"":h,z=c,v=l,m=(z.document,!!v.documentElement&&!!v.head&&"function"==typeof v.addEventListener&&v.createElement,~a.indexOf("MSIE")||a.indexOf("Trident/"),"___FONT_AWESOME___"),e=function(){try{return!0}catch(c){return!1}}();var s=z||{};s[m]||(s[m]={}),s[m].styles||(s[m].styles={}),s[m].hooks||(s[m].hooks={}),s[m].shims||(s[m].shims=[]);var t=s[m];function M(c,a){var l=(2<arguments.length&&void 0!==arguments[2]?arguments[2]:{}).skipHooks,h=void 0!==l&&l,z=Object.keys(a).reduce(function(c,l){var h=a[l];return!!h.icon?c[h.iconName]=h.icon:c[l]=h,c},{});"function"!=typeof t.hooks.addPack||h?t.styles[c]=function(z){for(var c=1;c<arguments.length;c++){var v=null!=arguments[c]?arguments[c]:{},l=Object.keys(v);"function"==typeof Object.getOwnPropertySymbols&&(l=l.concat(Object.getOwnPropertySymbols(v).filter(function(c){return Object.getOwnPropertyDescriptor(v,c).enumerable}))),l.forEach(function(c){var l,h,a;l=z,a=v[h=c],h in l?Object.defineProperty(l,h,{value:a,enumerable:!0,configurable:!0,writable:!0}):l[h]=a})}return z}({},t.styles[c]||{},z):t.hooks.addPack(c,z),"fas"===c&&M("fa",a)}var f={"500px":[448,512,[],"f26e","M103.3 344.3c-6.5-14.2-6.9-18.3 7.4-23.1 25.6-8 8 9.2 43.2 49.2h.3v-93.9c1.2-50.2 44-92.2 97.7-92.2 53.9 0 97.7 43.5 97.7 96.8 0 63.4-60.8 113.2-128.5 93.3-10.5-4.2-2.1-31.7 8.5-28.6 53 0 89.4-10.1 89.4-64.4 0-61-77.1-89.6-116.9-44.6-23.5 26.4-17.6 42.1-17.6 157.6 50.7 31 118.3 22 160.4-20.1 24.8-24.8 38.5-58 38.5-93 0-35.2-13.8-68.2-38.8-93.3-24.8-24.8-57.8-38.5-93.3-38.5s-68.8 13.8-93.5 38.5c-.3.3-16 16.5-21.2 23.9l-.5.6c-3.3 4.7-6.3 9.1-20.1 6.1-6.9-1.7-14.3-5.8-14.3-11.8V20c0-5 3.9-10.5 10.5-10.5h241.3c8.3 0 8.3 11.6 8.3 15.1 0 3.9 0 15.1-8.3 15.1H130.3v132.9h.3c104.2-109.8 282.8-36 282.8 108.9 0 178.1-244.8 220.3-310.1 62.8zm63.3-260.8c-.5 4.2 4.6 24.5 14.6 20.6C306 56.6 384 144.5 390.6 144.5c4.8 0 22.8-15.3 14.3-22.8-93.2-89-234.5-57-238.3-38.2zM393 414.7C283 524.6 94 475.5 61 310.5c0-12.2-30.4-7.4-28.9 3.3 24 173.4 246 256.9 381.6 121.3 6.9-7.8-12.6-28.4-20.7-20.4zM213.6 306.6c0 4 4.3 7.3 5.5 8.5 3 3 6.1 4.4 8.5 4.4 3.8 0 2.6.2 22.3-19.5 19.6 19.3 19.1 19.5 22.3 19.5 5.4 0 18.5-10.4 10.7-18.2L265.6 284l18.2-18.2c6.3-6.8-10.1-21.8-16.2-15.7L249.7 268c-18.6-18.8-18.4-19.5-21.5-19.5-5 0-18 11.7-12.4 17.3L234 284c-18.1 17.9-20.4 19.2-20.4 22.6z"],"accessible-icon":[448,512,[],"f368","M423.9 255.8L411 413.1c-3.3 40.7-63.9 35.1-60.6-4.9l10-122.5-41.1 2.3c10.1 20.7 15.8 43.9 15.8 68.5 0 41.2-16.1 78.7-42.3 106.5l-39.3-39.3c57.9-63.7 13.1-167.2-74-167.2-25.9 0-49.5 9.9-67.2 26L73 243.2c22-20.7 50.1-35.1 81.4-40.2l75.3-85.7-42.6-24.8-51.6 46c-30 26.8-70.6-18.5-40.5-45.4l68-60.7c9.8-8.8 24.1-10.2 35.5-3.6 0 0 139.3 80.9 139.5 81.1 16.2 10.1 20.7 36 6.1 52.6L285.7 229l106.1-5.9c18.5-1.1 33.6 14.4 32.1 32.7zm-64.9-154c28.1 0 50.9-22.8 50.9-50.9C409.9 22.8 387.1 0 359 0c-28.1 0-50.9 22.8-50.9 50.9 0 28.1 22.8 50.9 50.9 50.9zM179.6 456.5c-80.6 0-127.4-90.6-82.7-156.1l-39.7-39.7C36.4 287 24 320.3 24 356.4c0 130.7 150.7 201.4 251.4 122.5l-39.7-39.7c-16 10.9-35.3 17.3-56.1 17.3z"],accusoft:[640,512,[],"f369","M322.1 252v-1l-51.2-65.8s-12 1.6-25 15.1c-9 9.3-242.1 239.1-243.4 240.9-7 10 1.6 6.8 15.7 1.7.8 0 114.5-36.6 114.5-36.6.5-.6-.1-.1.6-.6-.4-5.1-.8-26.2-1-27.7-.6-5.2 2.2-6.9 7-8.9l92.6-33.8c.6-.8 88.5-81.7 90.2-83.3zm160.1 120.1c13.3 16.1 20.7 13.3 30.8 9.3 3.2-1.2 115.4-47.6 117.8-48.9 8-4.3-1.7-16.7-7.2-23.4-2.1-2.5-205.1-245.6-207.2-248.3-9.7-12.2-14.3-12.9-38.4-12.8-10.2 0-106.8.5-116.5.6-19.2.1-32.9-.3-19.2 16.9C250 75 476.5 365.2 482.2 372.1zm152.7 1.6c-2.3-.3-24.6-4.7-38-7.2 0 0-115 50.4-117.5 51.6-16 7.3-26.9-3.2-36.7-14.6l-57.1-74c-5.4-.9-60.4-9.6-65.3-9.3-3.1.2-9.6.8-14.4 2.9-4.9 2.1-145.2 52.8-150.2 54.7-5.1 2-11.4 3.6-11.1 7.6.2 2.5 2 2.6 4.6 3.5 2.7.8 300.9 67.6 308 69.1 15.6 3.3 38.5 10.5 53.6 1.7 2.1-1.2 123.8-76.4 125.8-77.8 5.4-4 4.3-6.8-1.7-8.2z"],"acquisitions-incorporated":[384,512,[],"f6af","M357.45 468.2c-1.2-7.7-1.3-7.6-9.6-7.6-99.8.2-111.8-2.4-112.7-2.6-12.3-1.7-20.6-10.5-21-23.1-.1-1.6-.2-71.6-1-129.1-.1-4.7 1.6-6.4 5.9-7.5 12.5-3 24.9-6.1 37.3-9.7 4.3-1.3 6.8-.2 8.4 3.5 4.5 10.3 8.8 20.6 13.2 30.9 1.6 3.7.1 4.4-3.4 4.4-10-.2-20-.1-30.4-.1v27h116c-1.4-9.5-2.7-18.1-4-27.5-7 0-13.8.4-20.4-.1-22.6-1.6-18.3-4.4-84-158.6-8.8-20.1-27.9-62.1-36.5-89.2-4.4-14 5.5-25.4 18.9-26.6 18.6-1.7 37.5-1.6 56.2-2 20.6-.4 41.2-.4 61.8-.5 3.1 0 4-1.4 4.3-4.3 1.2-9.8 2.7-19.5 4-29.2.8-5.3 1.6-10.7 2.4-16.1L23.75 0c-3.6 0-5.3 1.1-4.6 5.3 2.2 13.2-.8.8 6.4 45.3 63.4 0 71.8.9 101.8.5 12.3-.2 37 3.5 37.7 22.1.4 11.4-1.1 11.3-32.6 87.4-53.8 129.8-50.7 120.3-67.3 161-1.7 4.1-3.6 5.2-7.6 5.2-8.5-.2-17-.3-25.4.1-1.9.1-5.2 1.8-5.5 3.2-1.5 8.1-2.2 16.3-3.2 24.9h114.3v-27.6c-6.9 0-33.5.4-35.3-2.9 5.3-12.3 10.4-24.4 15.7-36.7 16.3 4 31.9 7.8 47.6 11.7 3.4.9 4.6 3 4.6 6.8-.1 42.9.1 85.9.2 128.8 0 10.2-5.5 19.1-14.9 23.1-6.5 2.7-3.3
Line truncated
|
||||
Reference in new issue
Block a user