1.3.0
This commit is contained in:
parent
1737a2f592
commit
25d47f7528
@ -3,6 +3,7 @@ Options -Indexes
|
||||
|
||||
DirectorySlash Off
|
||||
|
||||
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
|
||||
RewriteEngine On
|
||||
RewriteRule ^api(/.*)?$ /index.php?api=$1 [L,QSA]
|
||||
|
||||
|
2
cli.php
2
cli.php
@ -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,9 +128,12 @@ class Request {
|
||||
|
||||
// Logged in or api key authorized?
|
||||
if ($this->loginRequired) {
|
||||
if (isset($values['api_key']) && $this->apiKeyAllowed) {
|
||||
$apiKey = $values['api_key'];
|
||||
$apiKeyAuthorized = $this->user->authorize($apiKey);
|
||||
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) {
|
||||
@ -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();
|
||||
|
||||
|
@ -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) {
|
||||
if(strlen($username) < 5 || strlen($username) > 32) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -47,9 +47,9 @@ class VerifyCaptcha extends Request {
|
||||
$this->success = false;
|
||||
$this->lastError = "Could not verify captcha: No response from google received.";
|
||||
|
||||
if($response) {
|
||||
if ($response) {
|
||||
$this->success = $response["success"];
|
||||
if(!$this->success) {
|
||||
if (!$this->success) {
|
||||
$this->lastError = "Could not verify captcha: " . implode(";", $response["error-codes"]);
|
||||
} else {
|
||||
$score = $response["score"];
|
||||
|
@ -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 {
|
||||
|
22
core/Driver/SQL/Condition/Exists.class.php
Normal file
22
core/Driver/SQL/Condition/Exists.class.php
Normal file
@ -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();
|
||||
@ -302,8 +310,10 @@ abstract class SQL {
|
||||
}
|
||||
|
||||
return "NOT $expression";
|
||||
} else if($condition instanceof CondNull) {
|
||||
return $this->columnName($condition->getColumn()) . " IS NULL";
|
||||
} 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;
|
||||
|
7
core/External/ZipStream/File.php
vendored
7
core/External/ZipStream/File.php
vendored
@ -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;
|
||||
}
|
||||
|
126
core/Objects/AesStream.class.php
Normal file
126
core/Objects/AesStream.class.php
Normal file
@ -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";
|
||||
} else {
|
||||
$this->message = "Error confirming e-mail address: " . $req->getLastError();
|
||||
}
|
||||
} else {
|
||||
$this->success = false;
|
||||
$this->message = "The link you visited is no longer valid";
|
||||
}
|
||||
$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 {
|
||||
$("#confirm-status").addClass("alert-success");
|
||||
$("#confirm-status").text("Your e-mail address was successfully confirmed, you may now log in.");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$("#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>";
|
||||
}
|
||||
|
39
core/Views/Account/ResendConfirmEmail.class.php
Normal file
39
core/Views/Account/ResendConfirmEmail.class.php
Normal file
@ -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>
|
||||
|
@ -1,11 +1,11 @@
|
||||
<?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);
|
||||
if(file_exists($full_path)) {
|
||||
$full_path = WEBROOT . "/" . getClassPath($class);
|
||||
if (file_exists($full_path)) {
|
||||
include_once $full_path;
|
||||
} else {
|
||||
include_once getClassPath($class, false);
|
||||
@ -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)));
|
||||
|
||||
|
@ -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) {
|
||||
|
1
img/.htaccess
Normal file
1
img/.htaccess
Normal file
@ -0,0 +1 @@
|
||||
php_flag engine off
|
1
img/icons/files/.htaccess
Normal file
1
img/icons/files/.htaccess
Normal file
@ -0,0 +1 @@
|
||||
php_flag engine on
|
@ -4,9 +4,13 @@ $(document).ready(function () {
|
||||
return (typeof grecaptcha !== 'undefined');
|
||||
}
|
||||
|
||||
function showAlert(type, msg) {
|
||||
function showAlert(type, msg, raw=false) {
|
||||
let alert = $("#alertMessage");
|
||||
alert.text(msg);
|
||||
if (raw) {
|
||||
alert.html(msg);
|
||||
} else {
|
||||
alert.text(msg);
|
||||
}
|
||||
alert.attr("class", "mt-2 alert alert-" + type);
|
||||
alert.show();
|
||||
}
|
||||
@ -51,7 +55,11 @@ $(document).ready(function () {
|
||||
btn.prop("disabled", false);
|
||||
$("#password").val("");
|
||||
createdDiv.hide();
|
||||
showAlert("danger", res.msg);
|
||||
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("");
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
5
js/fontawesome-all.min.js
vendored
Normal file
5
js/fontawesome-all.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user