Compare commits
No commits in common. "master" and "1.0.1beta" have entirely different histories.
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,5 +3,4 @@
|
||||
/database.mwb.bak
|
||||
/nbproject/private
|
||||
*.sync-conflict*
|
||||
test*
|
||||
/conf/
|
||||
test*
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "static/css/material-color"]
|
||||
path = static/css/material-color
|
||||
url = https://source.netsyms.com/Netsyms/Material-Color
|
398
LICENSE.md
398
LICENSE.md
@ -1,362 +1,36 @@
|
||||
Copyright (c) 2017-2019 Netsyms Technologies. Some rights reserved.
|
||||
|
||||
Licensed under the Mozilla Public License Version 2.0. Files without MPL header
|
||||
comments, including third party code, may be under a different license.
|
||||
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
### 1. Definitions
|
||||
|
||||
**1.1. “Contributor”**
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
**1.2. “Contributor Version”**
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
**1.3. “Contribution”**
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
**1.4. “Covered Software”**
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
**1.5. “Incompatible With Secondary Licenses”**
|
||||
means
|
||||
|
||||
* **(a)** that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
* **(b)** that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
**1.6. “Executable Form”**
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
**1.7. “Larger Work”**
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
**1.8. “License”**
|
||||
means this document.
|
||||
|
||||
**1.9. “Licensable”**
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
**1.10. “Modifications”**
|
||||
means any of the following:
|
||||
|
||||
* **(a)** any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
* **(b)** any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
**1.11. “Patent Claims” of a Contributor**
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
**1.12. “Secondary License”**
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
**1.13. “Source Code Form”**
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
**1.14. “You” (or “Your”)**
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, “You” includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, “control” means **(a)** the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or **(b)** ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
|
||||
### 2. License Grants and Conditions
|
||||
|
||||
#### 2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
* **(a)** under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
* **(b)** under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
#### 2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
#### 2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
* **(a)** for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
* **(b)** for infringements caused by: **(i)** Your and any other third party's
|
||||
modifications of Covered Software, or **(ii)** the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
* **(c)** under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
#### 2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
#### 2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
#### 2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
#### 2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
|
||||
### 3. Responsibilities
|
||||
|
||||
#### 3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
#### 3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
* **(a)** such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
* **(b)** You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
#### 3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
#### 3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
#### 3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
|
||||
### 4. Inability to Comply Due to Statute or Regulation
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: **(a)** comply with
|
||||
the terms of this License to the maximum extent possible; and **(b)**
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
|
||||
### 5. Termination
|
||||
|
||||
**5.1.** The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated **(a)** provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and **(b)** on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
**5.2.** If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
**5.3.** In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
|
||||
### 6. Disclaimer of Warranty
|
||||
|
||||
> Covered Software is provided under this License on an “as is”
|
||||
> basis, without warranty of any kind, either expressed, implied, or
|
||||
> statutory, including, without limitation, warranties that the
|
||||
> Covered Software is free of defects, merchantable, fit for a
|
||||
> particular purpose or non-infringing. The entire risk as to the
|
||||
> quality and performance of the Covered Software is with You.
|
||||
> Should any Covered Software prove defective in any respect, You
|
||||
> (not any Contributor) assume the cost of any necessary servicing,
|
||||
> repair, or correction. This disclaimer of warranty constitutes an
|
||||
> essential part of this License. No use of any Covered Software is
|
||||
> authorized under this License except under this disclaimer.
|
||||
|
||||
### 7. Limitation of Liability
|
||||
|
||||
> Under no circumstances and under no legal theory, whether tort
|
||||
> (including negligence), contract, or otherwise, shall any
|
||||
> Contributor, or anyone who distributes Covered Software as
|
||||
> permitted above, be liable to You for any direct, indirect,
|
||||
> special, incidental, or consequential damages of any character
|
||||
> including, without limitation, damages for lost profits, loss of
|
||||
> goodwill, work stoppage, computer failure or malfunction, or any
|
||||
> and all other commercial damages or losses, even if such party
|
||||
> shall have been informed of the possibility of such damages. This
|
||||
> limitation of liability shall not apply to liability for death or
|
||||
> personal injury resulting from such party's negligence to the
|
||||
> extent applicable law prohibits such limitation. Some
|
||||
> jurisdictions do not allow the exclusion or limitation of
|
||||
> incidental or consequential damages, so this exclusion and
|
||||
> limitation may not apply to You.
|
||||
|
||||
|
||||
### 8. Litigation
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
|
||||
### 9. Miscellaneous
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
|
||||
### 10. Versions of the License
|
||||
|
||||
#### 10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
#### 10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
#### 10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
#### 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
## Exhibit A - Source Code Form License Notice
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
## Exhibit B - “Incompatible With Secondary Licenses” Notice
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
|
||||
|
||||
Copyright (C) 2017 Netsyms Technologies. All rights reserved.
|
||||
|
||||
**Definitions:**
|
||||
* "the code", "the software", or "code" refers to the application (or any portion
|
||||
thereof, in any form) this license is included with, excluding third-party libraries.
|
||||
* "any form" means source code, binaries, or any other representation of the code.
|
||||
* "explicit written permission" refers to a written, signed, non-transferable
|
||||
statement from Netsyms Technologies granting you additional rights to the code.
|
||||
|
||||
**You are allowed to:**
|
||||
* use the code for personal and non-commercial purposes.
|
||||
* modify the code for personal and non-commercial purposes.
|
||||
|
||||
**You are NOT allowed to:**
|
||||
* redistribute the code in any form.
|
||||
* sell the code in any form.
|
||||
* use the code for any projects you redistribute or sell, unless you have
|
||||
explicit written permission.
|
||||
* use the code for any projects that are not both personal and non-commercial.
|
||||
* use the code for commercial, business, or non-profit purposes without
|
||||
explicit written permission.
|
||||
* do anything else not permitted in this license.
|
||||
|
||||
**You must:**
|
||||
* retain any and all copyright notices in the code.
|
||||
* retain any and all license notices in the code.
|
||||
* ask for clarification from Netsyms Technologies if any portion of this license
|
||||
is unclear or ambiguous.
|
||||
|
||||
**Disclaimer:**
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
33
README.md
33
README.md
@ -1,33 +0,0 @@
|
||||
AccountHub
|
||||
======
|
||||
|
||||
AccountHub is a web application enabling secure self-serve account management.
|
||||
Employees can change their password and manage other web apps they have access
|
||||
to with the dashboard.
|
||||
|
||||
https://netsyms.biz/apps/accounthub
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
0. Setup a LAMP server with PHP 7.2, including PHP extensions mbstring, zip, gd, and imagick
|
||||
1. Copy `settings.template.php` to `settings.php`
|
||||
2. Import `database.sql` into your database server
|
||||
3. Edit `settings.php` and fill in your DB info
|
||||
4. Set the URL of the install
|
||||
5. Setup "EXTERNAL_APPS" with specifics for your install.
|
||||
6. Setup the email settings to receive alerts you configure later in ManagePanel
|
||||
7. Run `composer install` (or `composer.phar install`) to install dependency libraries
|
||||
8. Edit the database table `apikeys` and add some API keys for the other apps to use
|
||||
9. From a web browser, visit `http://apps/url` (or whatever your setup is). If you did everything right, you should see a login screen.
|
||||
10. Now go to `http://apps/url/setup.php` and create an admin account.
|
||||
11. Install [ManagePanel](https://source.netsyms.com/Business/ManagePanel) to setup additional user accounts.
|
||||
|
||||
|
||||
Upgrading
|
||||
---------
|
||||
|
||||
1. Run `git pull` or otherwise update the code
|
||||
2. Run `composer install` to update dependencies
|
||||
3. Execute the SQL scripts in `database_upgrade` to take you from your current version to the latest version
|
||||
4. Rewrite your `settings.php` based on the new `settings.template.php`
|
108
action.php
108
action.php
@ -1,15 +1,12 @@
|
||||
<?php
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Make things happen when buttons are pressed and forms submitted.
|
||||
*/
|
||||
require_once __DIR__ . "/required.php";
|
||||
use LdapTools\LdapManager;
|
||||
use LdapTools\Object\LdapObjectType;
|
||||
|
||||
use OTPHP\TOTP;
|
||||
require_once __DIR__ . "/required.php";
|
||||
|
||||
// If the user presses Sign Out but we're not logged in anymore,
|
||||
// we don't want to show a nasty error.
|
||||
@ -21,100 +18,49 @@ if ($VARS['action'] == 'signout' && $_SESSION['loggedin'] != true) {
|
||||
|
||||
dieifnotloggedin();
|
||||
|
||||
require_once __DIR__ . "/lib/login.php";
|
||||
|
||||
function returnToSender($msg, $arg = "") {
|
||||
global $VARS;
|
||||
$header = "Location: app.php?page=" . urlencode($VARS['source']) . "&msg=$msg";
|
||||
if ($arg != "") {
|
||||
$header .= "&arg=$arg";
|
||||
if ($arg == "") {
|
||||
header("Location: home.php?page=" . urlencode($VARS['source']) . "&msg=$msg");
|
||||
} else {
|
||||
header("Location: home.php?page=" . urlencode($VARS['source']) . "&msg=$msg&arg=" . urlencode($arg));
|
||||
}
|
||||
header($header);
|
||||
die();
|
||||
}
|
||||
|
||||
switch ($VARS['action']) {
|
||||
case "signout":
|
||||
Log::insert(LogType::LOGOUT, $_SESSION['uid']);
|
||||
insertAuthLog(11, $_SESSION['uid']);
|
||||
session_destroy();
|
||||
header('Location: index.php?logout=1');
|
||||
header('Location: index.php');
|
||||
die("Logged out.");
|
||||
case "chpasswd":
|
||||
engageRateLimit();
|
||||
$error = [];
|
||||
$user = new User($_SESSION['uid']);
|
||||
try {
|
||||
$result = $user->changePassword($VARS['oldpass'], $VARS['newpass'], $VARS['conpass']);
|
||||
|
||||
if ($result === TRUE) {
|
||||
returnToSender("password_updated");
|
||||
}
|
||||
} catch (PasswordMatchException $e) {
|
||||
returnToSender("passwords_same");
|
||||
} catch (PasswordMismatchException $e) {
|
||||
returnToSender("new_password_mismatch");
|
||||
} catch (IncorrectPasswordException $e) {
|
||||
returnToSender("old_password_mismatch");
|
||||
} catch (WeakPasswordException $e) {
|
||||
returnToSender("weak_password");
|
||||
$result = change_password($VARS['oldpass'], $VARS['newpass'], $VARS['conpass'], $error);
|
||||
if ($result === TRUE) {
|
||||
returnToSender("password_updated");
|
||||
}
|
||||
break;
|
||||
case "chpin":
|
||||
engageRateLimit();
|
||||
$error = [];
|
||||
if (!($VARS['newpin'] == "" || (is_numeric($VARS['newpin']) && strlen($VARS['newpin']) >= 1 && strlen($VARS['newpin']) <= 8))) {
|
||||
returnToSender("invalid_pin_format");
|
||||
switch (count($error)) {
|
||||
case 1:
|
||||
returnToSender($error[0]);
|
||||
case 2:
|
||||
returnToSender($error[0], $error[1]);
|
||||
default:
|
||||
returnToSender("generic_op_error");
|
||||
}
|
||||
if ($VARS['newpin'] == $VARS['conpin']) {
|
||||
$database->update('accounts', ['pin' => ($VARS['newpin'] == "" ? null : $VARS['newpin'])], ['uid' => $_SESSION['uid']]);
|
||||
returnToSender("pin_updated");
|
||||
}
|
||||
returnToSender("new_pin_mismatch");
|
||||
break;
|
||||
case "add2fa":
|
||||
if (empty($VARS['secret'])) {
|
||||
if (is_empty($VARS['secret'])) {
|
||||
returnToSender("invalid_parameters");
|
||||
}
|
||||
$user = new User($_SESSION['uid']);
|
||||
$totp = new TOTP(null, $VARS['secret']);
|
||||
if (!$totp->verify($VARS["totpcode"])) {
|
||||
returnToSender("2fa_wrong_code");
|
||||
}
|
||||
$user->save2fa($VARS['secret']);
|
||||
Log::insert(LogType::ADDED_2FA, $user);
|
||||
$database->update('accounts', ['authsecret' => $VARS['secret']], ['uid' => $_SESSION['uid']]);
|
||||
insertAuthLog(9, $_SESSION['uid']);
|
||||
returnToSender("2fa_enabled");
|
||||
case "rm2fa":
|
||||
engageRateLimit();
|
||||
(new User($_SESSION['uid']))->save2fa("");
|
||||
Log::insert(LogType::REMOVED_2FA, $_SESSION['uid']);
|
||||
$database->update('accounts', ['authsecret' => ""], ['uid' => $_SESSION['uid']]);
|
||||
insertAuthLog(10, $_SESSION['uid']);
|
||||
returnToSender("2fa_removed");
|
||||
break;
|
||||
case "readnotification":
|
||||
$user = new User($_SESSION['uid']);
|
||||
|
||||
if (empty($VARS['id'])) {
|
||||
returnToSender("invalid_parameters#notifications");
|
||||
}
|
||||
try {
|
||||
Notifications::read($user, $VARS['id']);
|
||||
returnToSender("#notifications");
|
||||
} catch (Exception $ex) {
|
||||
returnToSender("invalid_parameters#notifications");
|
||||
}
|
||||
break;
|
||||
case "deletenotification":
|
||||
$user = new User($_SESSION['uid']);
|
||||
|
||||
if (empty($VARS['id'])) {
|
||||
returnToSender("invalid_parameters#notifications");
|
||||
}
|
||||
try {
|
||||
Notifications::delete($user, $VARS['id']);
|
||||
returnToSender("notification_deleted#notifications");
|
||||
} catch (Exception $ex) {
|
||||
returnToSender("invalid_parameters#notifications");
|
||||
}
|
||||
break;
|
||||
case "resetfeedkey":
|
||||
$database->delete('userkeys', ['AND' => ['uid' => $_SESSION['uid'], 'typeid' => 1]]);
|
||||
returnToSender("feed_key_reset");
|
||||
break;
|
||||
}
|
||||
}
|
264
api.php
264
api.php
@ -1,9 +1,263 @@
|
||||
<?php
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
/**
|
||||
* Simple JSON API to allow other apps to access accounts in this system.
|
||||
*
|
||||
* Requests can be sent via either GET or POST requests. POST is recommended
|
||||
* as it has a lower chance of being logged on the server, exposing unencrypted
|
||||
* user passwords.
|
||||
*/
|
||||
require __DIR__ . '/required.php';
|
||||
require_once __DIR__ . '/lib/login.php';
|
||||
header("Content-Type: application/json");
|
||||
|
||||
//try {
|
||||
$key = $VARS['key'];
|
||||
if ($database->has('apikeys', ['key' => $key]) !== TRUE) {
|
||||
header("HTTP/1.1 403 Unauthorized");
|
||||
insertAuthLog(14, null, "Key: " . $key);
|
||||
die("\"403 Unauthorized\"");
|
||||
}
|
||||
|
||||
// Load in new API from legacy location (a.k.a. here)
|
||||
require __DIR__ . "/api/index.php";
|
||||
/**
|
||||
* Get the API key with most of the characters replaced with *s.
|
||||
* @global string $key
|
||||
* @return string
|
||||
*/
|
||||
function getCensoredKey() {
|
||||
global $key;
|
||||
$resp = $key;
|
||||
if (strlen($key) > 5) {
|
||||
for ($i = 2; $i < strlen($key) - 2; $i++) {
|
||||
$resp[$i] = "*";
|
||||
}
|
||||
}
|
||||
return $resp;
|
||||
}
|
||||
|
||||
switch ($VARS['action']) {
|
||||
case "ping":
|
||||
exit(json_encode(["status" => "OK"]));
|
||||
break;
|
||||
case "auth":
|
||||
$errmsg = "";
|
||||
if (authenticate_user($VARS['username'], $VARS['password'], $errmsg)) {
|
||||
insertAuthLog(12, null, "Username: " . $VARS['username'] . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "OK", "msg" => lang("login successful", false)]));
|
||||
} else {
|
||||
insertAuthLog(13, $uid, "Username: " . $VARS['username'] . ", Key: " . getCensoredKey());
|
||||
if (!is_empty($errmsg)) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang2("ldap error", ['error' => $errmsg], false)]));
|
||||
}
|
||||
if (user_exists($VARS['username'])) {
|
||||
switch (get_account_status($VARS['username'])) {
|
||||
case "LOCKED_OR_DISABLED":
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("account locked", false)]));
|
||||
case "TERMINATED":
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("account terminated", false)]));
|
||||
case "CHANGE_PASSWORD":
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("password expired", false)]));
|
||||
case "NORMAL":
|
||||
break;
|
||||
default:
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("account state error", false)]));
|
||||
}
|
||||
}
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)]));
|
||||
}
|
||||
break;
|
||||
case "userinfo":
|
||||
if (!is_empty($VARS['username'])) {
|
||||
if (user_exists_local($VARS['username'])) {
|
||||
$data = $database->select("accounts", ["uid", "username", "realname (name)", "email", "phone" => ["phone1 (1)", "phone2 (2)"]], ["username" => $VARS['username']])[0];
|
||||
exit(json_encode(["status" => "OK", "data" => $data]));
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)]));
|
||||
}
|
||||
} else if (!is_empty($VARS['uid'])) {
|
||||
if ($database->has('accounts', ['uid' => $VARS['uid']])) {
|
||||
$data = $database->select("accounts", ["uid", "username", "realname (name)", "email", "phone" => ["phone1 (1)", "phone2 (2)"]], ["uid" => $VARS['uid']])[0];
|
||||
exit(json_encode(["status" => "OK", "data" => $data]));
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)]));
|
||||
}
|
||||
} else {
|
||||
header("HTTP/1.1 400 Bad Request");
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
break;
|
||||
case "userexists":
|
||||
if (!is_empty($VARS['uid'])) {
|
||||
if ($database->has('accounts', ['uid' => $VARS['uid']])) {
|
||||
exit(json_encode(["status" => "OK", "exists" => true]));
|
||||
} else {
|
||||
exit(json_encode(["status" => "OK", "exists" => false]));
|
||||
}
|
||||
}
|
||||
if (user_exists_local($VARS['username'])) {
|
||||
exit(json_encode(["status" => "OK", "exists" => true]));
|
||||
} else {
|
||||
exit(json_encode(["status" => "OK", "exists" => false]));
|
||||
}
|
||||
break;
|
||||
case "hastotp":
|
||||
if (userHasTOTP($VARS['username'])) {
|
||||
exit(json_encode(["status" => "OK", "otp" => true]));
|
||||
} else {
|
||||
exit(json_encode(["status" => "OK", "otp" => false]));
|
||||
}
|
||||
break;
|
||||
case "verifytotp":
|
||||
if (verifyTOTP($VARS['username'], $VARS['code'])) {
|
||||
exit(json_encode(["status" => "OK", "valid" => true]));
|
||||
} else {
|
||||
insertAuthLog(7, null, "Username: " . $VARS['username'] . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("2fa incorrect", false), "valid" => false]));
|
||||
}
|
||||
break;
|
||||
case "acctstatus":
|
||||
exit(json_encode(["status" => "OK", "account" => get_account_status($VARS['username'])]));
|
||||
case "login":
|
||||
// simulate a login, checking account status and alerts
|
||||
$errmsg = "";
|
||||
if (authenticate_user($VARS['username'], $VARS['password'], $errmsg)) {
|
||||
$uid = $database->select('accounts', 'uid', ['username' => $VARS['username']])[0];
|
||||
switch (get_account_status($VARS['username'])) {
|
||||
case "LOCKED_OR_DISABLED":
|
||||
insertAuthLog(5, $uid, "Username: " . $VARS['username'] . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("account locked", false)]));
|
||||
case "TERMINATED":
|
||||
insertAuthLog(5, $uid, "Username: " . $VARS['username'] . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("account terminated", false)]));
|
||||
case "CHANGE_PASSWORD":
|
||||
insertAuthLog(5, $uid, "Username: " . $VARS['username'] . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("password expired", false)]));
|
||||
case "NORMAL":
|
||||
insertAuthLog(4, $uid, "Username: " . $VARS['username'] . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "OK"]));
|
||||
case "ALERT_ON_ACCESS":
|
||||
sendLoginAlertEmail($VARS['username']);
|
||||
insertAuthLog(4, $uid, "Username: " . $VARS['username'] . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "OK", "alert" => true]));
|
||||
default:
|
||||
insertAuthLog(5, $uid, "Username: " . $VARS['username'] . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("account state error", false)]));
|
||||
}
|
||||
} else {
|
||||
insertAuthLog(5, null, "Username: " . $VARS['username'] . ", Key: " . getCensoredKey());
|
||||
if (!is_empty($errmsg)) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang2("ldap error", ['error' => $errmsg], false)]));
|
||||
}
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)]));
|
||||
}
|
||||
break;
|
||||
case "ismanagerof":
|
||||
if ($VARS['uid'] === 1) {
|
||||
if ($database->has("accounts", ['uid' => $VARS['manager']])) {
|
||||
if ($database->has("accounts", ['uid' => $VARS['employee']])) {
|
||||
$managerid = $VARS['manager'];
|
||||
$employeeid = $VARS['employee'];
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false), "user" => $VARS['employee']]));
|
||||
}
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false), "user" => $VARS['manager']]));
|
||||
}
|
||||
} else {
|
||||
if (user_exists_local($VARS['manager'])) {
|
||||
if (user_exists_local($VARS['employee'])) {
|
||||
$managerid = $database->select('accounts', 'uid', ['username' => $VARS['manager']]);
|
||||
$employeeid = $database->select('accounts', 'uid', ['username' => $VARS['employee']]);
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false), "user" => $VARS['employee']]));
|
||||
}
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false), "user" => $VARS['manager']]));
|
||||
}
|
||||
}
|
||||
if ($database->has('managers', ['AND' => ['managerid' => $managerid, 'employeeid' => $employeeid]])) {
|
||||
exit(json_encode(["status" => "OK", "managerof" => true]));
|
||||
} else {
|
||||
exit(json_encode(["status" => "OK", "managerof" => false]));
|
||||
}
|
||||
break;
|
||||
case "getmanaged":
|
||||
if ($VARS['uid']) {
|
||||
if ($database->has("accounts", ['uid' => $VARS['uid']])) {
|
||||
$managerid = $VARS['uid'];
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false)]));
|
||||
}
|
||||
} else if ($VARS['username']) {
|
||||
if ($database->has("accounts", ['username' => $VARS['username']])) {
|
||||
$managerid = $database->select('accounts', 'uid', ['username' => $VARS['username']]);
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false)]));
|
||||
}
|
||||
} else {
|
||||
header("HTTP/1.1 400 Bad Request");
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
$managed = $database->select('managers', 'employeeid', ['managerid' => $managerid]);
|
||||
exit(json_encode(["status" => "OK", "employees" => $managed]));
|
||||
break;
|
||||
case "getmanagers":
|
||||
if ($VARS['uid']) {
|
||||
if ($database->has("accounts", ['uid' => $VARS['uid']])) {
|
||||
$empid = $VARS['uid'];
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false)]));
|
||||
}
|
||||
} else if ($VARS['username']) {
|
||||
if ($database->has("accounts", ['username' => $VARS['username']])) {
|
||||
$empid = $database->select('accounts', 'uid', ['username' => $VARS['username']]);
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false)]));
|
||||
}
|
||||
} else {
|
||||
header("HTTP/1.1 400 Bad Request");
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
$managers = $database->select('managers', 'managerid', ['employeeid' => $empid]);
|
||||
exit(json_encode(["status" => "OK", "managers" => $managers]));
|
||||
break;
|
||||
case "usersearch":
|
||||
if (is_empty($VARS['search']) || strlen($VARS['search']) < 3) {
|
||||
exit(json_encode(["status" => "OK", "result" => []]));
|
||||
}
|
||||
$data = $database->select('accounts', ['uid', 'username', 'realname (name)'], ["OR" => ['username[~]' => $VARS['search'], 'realname[~]' => $VARS['search']], "LIMIT" => 10]);
|
||||
exit(json_encode(["status" => "OK", "result" => $data]));
|
||||
break;
|
||||
case "permission":
|
||||
if (is_empty($VARS['code'])) {
|
||||
header("HTTP/1.1 400 Bad Request");
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
$perm = $VARS['code'];
|
||||
if ($VARS['uid']) {
|
||||
if ($database->has("accounts", ['uid' => $VARS['uid']])) {
|
||||
$user = $database->select('accounts', ['username'], ['uid' => $VARS['uid']])[0]['username'];
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false)]));
|
||||
}
|
||||
} else if ($VARS['username']) {
|
||||
if ($database->has("accounts", ['username' => $VARS['username']])) {
|
||||
$user = $VARS['username'];
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false)]));
|
||||
}
|
||||
} else {
|
||||
header("HTTP/1.1 400 Bad Request");
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
$hasperm = account_has_permission($user, $perm);
|
||||
exit(json_encode(["status" => "OK", "has_permission" => $hasperm]));
|
||||
break;
|
||||
default:
|
||||
header("HTTP/1.1 400 Bad Request");
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
/* } catch (Exception $e) {
|
||||
header("HTTP/1.1 500 Internal Server Error");
|
||||
die("\"500 Internal Server Error\"");
|
||||
} */
|
@ -1,5 +0,0 @@
|
||||
# Rewrite for Nextcloud Notes API
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine on
|
||||
RewriteRule ([a-zA-Z0-9]+) index.php?action=$1 [PT]
|
||||
</IfModule>
|
@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
sendJsonResp(null, "OK", ["account" => User::byUsername($VARS['username'])->getStatus()->getString()]);
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$code = strtoupper(substr(md5(mt_rand() . uniqid("", true)), 0, 20));
|
||||
$desc = htmlspecialchars($VARS['desc']);
|
||||
$chunk_code = str_replace(" ", "-", trim(chunk_split($code, 5, ' ')));
|
||||
$database->insert('apppasswords', ['uid' => User::byUsername($VARS['username'])->getUID(), 'hash' => password_hash($chunk_code, PASSWORD_DEFAULT), 'description' => $desc]);
|
||||
|
||||
sendJsonResp("", "OK", ["pass" => $chunk_code]);
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
}
|
||||
|
||||
try {
|
||||
$timestamp = "";
|
||||
if (!empty($VARS['timestamp'])) {
|
||||
$timestamp = date("Y-m-d H:i:s", strtotime($VARS['timestamp']));
|
||||
}
|
||||
$url = "";
|
||||
if (!empty($VARS['url'])) {
|
||||
$url = $VARS['url'];
|
||||
}
|
||||
$nid = Notifications::add($user, $VARS['title'], $VARS['content'], $timestamp, $url, isset($VARS['sensitive']));
|
||||
|
||||
exitWithJson(["status" => "OK", "id" => $nid]);
|
||||
} catch (Exception $ex) {
|
||||
sendJsonResp($ex->getMessage(), "ERROR");
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
engageRateLimit();
|
||||
$appname = "???";
|
||||
if (!empty($VARS['appname'])) {
|
||||
$appname = $VARS['appname'];
|
||||
}
|
||||
$result = User::byUsername($VARS['username'])->sendAlertEmail($appname);
|
||||
if ($result === TRUE) {
|
||||
sendJsonResp();
|
||||
}
|
||||
sendJsonResp($result, "ERROR");
|
@ -1,39 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$user = User::byUsername($VARS['username']);
|
||||
|
||||
$ok = false;
|
||||
if (empty($VARS['apppass']) && ($user->checkPassword($VARS['password']) || $user->checkAppPassword($VARS['password']))) {
|
||||
$ok = true;
|
||||
} else {
|
||||
if ((!$user->has2fa() && $user->checkPassword($VARS['password'])) || $user->checkAppPassword($VARS['password'])) {
|
||||
$ok = true;
|
||||
}
|
||||
}
|
||||
if ($ok) {
|
||||
Log::insert(LogType::API_AUTH_OK, null, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
sendJsonResp($Strings->get("login successful", false), "OK");
|
||||
} else {
|
||||
Log::insert(LogType::API_AUTH_FAILED, $user->getUID(), "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
if ($user->exists()) {
|
||||
switch ($user->getStatus()->get()) {
|
||||
case AccountStatus::LOCKED_OR_DISABLED:
|
||||
sendJsonResp($Strings->get("account locked", false), "ERROR");
|
||||
case AccountStatus::TERMINATED:
|
||||
sendJsonResp($Strings->get("account terminated", false), "ERROR");
|
||||
case AccountStatus::CHANGE_PASSWORD:
|
||||
sendJsonResp($Strings->get("password expired", false), "ERROR");
|
||||
case AccountStatus::NORMAL:
|
||||
break;
|
||||
default:
|
||||
sendJsonResp($Strings->get("account state error", false), "ERROR");
|
||||
}
|
||||
}
|
||||
sendJsonResp($Strings->get("login incorrect", false), "ERROR");
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
try {
|
||||
$uid = LoginKey::getuid($VARS['code']);
|
||||
|
||||
exitWithJson(["status" => "OK", "uid" => $uid]);
|
||||
} catch (Exception $ex) {
|
||||
sendJsonResp("", "ERROR");
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$pin = "";
|
||||
if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
}
|
||||
|
||||
if ($user->exists()) {
|
||||
$pin = $database->get("accounts", "pin", ["uid" => $user->getUID()]);
|
||||
} else {
|
||||
sendJsonResp($Strings->get("login incorrect", false), "ERROR");
|
||||
}
|
||||
if (is_null($pin) || $pin == "") {
|
||||
exitWithJson(["status" => "ERROR", "pinvalid" => false, "nopinset" => true]);
|
||||
}
|
||||
exitWithJson(["status" => "OK", "pinvalid" => ($pin == $VARS['pin'])]);
|
@ -1,15 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$database->delete("onetimekeys", ["expires[<]" => date("Y-m-d H:i:s")]); // cleanup
|
||||
if ($database->has("onetimekeys", ["key" => $VARS['code'], "expires[>]" => date("Y-m-d H:i:s")])) {
|
||||
$user = $database->get("onetimekeys", ["[>]accounts" => ["uid" => "uid"]], ["username", "realname", "accounts.uid"], ["key" => $VARS['code']]);
|
||||
exitWithJson(["status" => "OK", "user" => $user]);
|
||||
} else {
|
||||
sendJsonResp($Strings->get("no such code or code expired", false), "ERROR");
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
}
|
||||
|
||||
try {
|
||||
Notifications::delete($user, $VARS['id']);
|
||||
sendJsonResp();
|
||||
} catch (Exception $ex) {
|
||||
sendJsonResp($ex->getMessage(), "ERROR");
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$groups = $database->select('groups', ['groupid (id)', 'groupname (name)']);
|
||||
exitWithJson(["status" => "OK", "groups" => $groups]);
|
@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (!empty($VARS['uid'])) {
|
||||
if ($database->has("accounts", ['uid' => $VARS['uid']])) {
|
||||
$empid = $VARS['uid'];
|
||||
} else {
|
||||
sendJsonResp($Strings->get("user does not exist", false), "ERROR");
|
||||
}
|
||||
} else if (!empty($VARS['username'])) {
|
||||
if ($database->has("accounts", ['username' => strtolower($VARS['username'])])) {
|
||||
$empid = $database->select('accounts', 'uid', ['username' => strtolower($VARS['username'])]);
|
||||
} else {
|
||||
sendJsonResp($Strings->get("user does not exist", false), "ERROR");
|
||||
}
|
||||
}
|
||||
$groups = $database->select('assigned_groups', ["[>]groups" => ["groupid" => "groupid"]], ['groups.groupid (id)', 'groups.groupname (name)'], ['uid' => $empid]);
|
||||
exitWithJson(["status" => "OK", "groups" => $groups]);
|
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$appicon = null;
|
||||
if (!empty($VARS['appicon'])) {
|
||||
$appicon = $VARS['appicon'];
|
||||
}
|
||||
|
||||
$code = LoginKey::generate($VARS['appname'], $appicon);
|
||||
|
||||
if (strpos($SETTINGS['url'], "http") === 0) {
|
||||
$url = $SETTINGS['url'] . "login/";
|
||||
} else {
|
||||
$url = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . (($_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) ? ":" . $_SERVER['SERVER_PORT'] : "") . $SETTINGS['url'] . "login/";
|
||||
}
|
||||
|
||||
exitWithJson(["status" => "OK", "code" => $code, "loginurl" => $url]);
|
@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (!empty($VARS['uid'])) {
|
||||
$manager = new User($VARS['uid']);
|
||||
} else if (!empty($VARS['username'])) {
|
||||
$manager = User::byUsername($VARS['username']);
|
||||
}
|
||||
|
||||
if (!$manager->exists()) {
|
||||
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false)]);
|
||||
}
|
||||
if (!empty($VARS['get']) && $VARS['get'] == "username") {
|
||||
$managed = $database->select('managers', ['[>]accounts' => ['employeeid' => 'uid']], 'username', ['managerid' => $manager->getUID()]);
|
||||
} else {
|
||||
$managed = $database->select('managers', 'employeeid', ['managerid' => $manager->getUID()]);
|
||||
}
|
||||
exitWithJson(["status" => "OK", "employees" => $managed]);
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (!empty($VARS['uid'])) {
|
||||
$emp = new User($VARS['uid']);
|
||||
} else if (!empty($VARS['username'])) {
|
||||
$emp = User::byUsername($VARS['username']);
|
||||
}
|
||||
|
||||
if (!$emp->exists()) {
|
||||
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false)]);
|
||||
}
|
||||
$managers = $database->select('managers', 'managerid', ['employeeid' => $emp->getUID()]);
|
||||
exitWithJson(["status" => "OK", "managers" => $managers]);
|
@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
}
|
||||
|
||||
try {
|
||||
$notifications = Notifications::get($user);
|
||||
exitWithJson(["status" => "OK", "notifications" => $notifications]);
|
||||
} catch (Exception $ex) {
|
||||
sendJsonResp($ex->getMessage(), "ERROR");
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if ($database->has("groups", ['groupid' => $VARS['gid']])) {
|
||||
$groupid = $VARS['gid'];
|
||||
} else {
|
||||
sendJsonResp($Strings->get("group does not exist", false), "ERROR");
|
||||
}
|
||||
|
||||
if (!empty($VARS["get"]) && $VARS['get'] == "username") {
|
||||
$users = $database->select('assigned_groups', ['[>]accounts' => ['uid' => 'uid']], 'username', ['groupid' => $groupid, "ORDER" => "username"]);
|
||||
} else if (!empty($VARS["get"]) && $VARS['get'] == "detail") {
|
||||
$users = $database->select('assigned_groups', ['[>]accounts' => ['uid' => 'uid']], ['username', 'realname (name)', 'accounts.uid', 'pin'], ['groupid' => $groupid, "ORDER" => "realname"]);
|
||||
for ($i = 0; $i < count($users); $i++) {
|
||||
if (is_null($users[$i]['pin']) || $users[$i]['pin'] == "") {
|
||||
$users[$i]['pin'] = false;
|
||||
} else {
|
||||
$users[$i]['pin'] = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$users = $database->select('assigned_groups', 'uid', ['groupid' => $groupid]);
|
||||
}
|
||||
exitWithJson(["status" => "OK", "users" => $users]);
|
@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (strlen($VARS['search']) < 2) {
|
||||
exitWithJson(["status" => "OK", "result" => []]);
|
||||
}
|
||||
$data = $database->select('groups', ['groupid (id)', 'groupname (name)'], ['groupname[~]' => $VARS['search'], "LIMIT" => 10]);
|
||||
exitWithJson(["status" => "OK", "result" => $data]);
|
@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
sendJsonResp(null, "OK", ["otp" => User::byUsername($VARS['username'])->has2fa()]);
|
@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (!empty($VARS['uid']) && $VARS['uid'] == "1") {
|
||||
$manager = new User($VARS['manager']);
|
||||
$employee = new User($VARS['employee']);
|
||||
} else {
|
||||
$manager = User::byUsername($VARS['manager']);
|
||||
$employee = User::byUsername($VARS['employee']);
|
||||
}
|
||||
if (!$manager->exists()) {
|
||||
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false), "user" => $VARS['manager']]);
|
||||
}
|
||||
if (!$employee->exists()) {
|
||||
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false), "user" => $VARS['employee']]);
|
||||
}
|
||||
|
||||
if ($database->has('managers', ['AND' => ['managerid' => $manager->getUID(), 'employeeid' => $employee->getUID()]])) {
|
||||
exitWithJson(["status" => "OK", "managerof" => true]);
|
||||
} else {
|
||||
exitWithJson(["status" => "OK", "managerof" => false]);
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$apps = $SETTINGS['apps'];
|
||||
// Format paths as absolute URLs
|
||||
foreach ($apps as $k => $v) {
|
||||
if (strpos($apps[$k]['url'], "http") === FALSE) {
|
||||
$apps[$k]['url'] = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . ($_SERVER['SERVER_PORT'] != 80 || $_SERVER['SERVER_PORT'] != 443 ? ":" . $_SERVER['SERVER_PORT'] : "") . $apps[$k]['url'];
|
||||
}
|
||||
}
|
||||
exitWithJson(["status" => "OK", "apps" => $apps]);
|
@ -1,46 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
engageRateLimit();
|
||||
$user = User::byUsername($VARS['username']);
|
||||
|
||||
$ok = false;
|
||||
if (empty($VARS['apppass']) && ($user->checkPassword($VARS['password']) || $user->checkAppPassword($VARS['password']))) {
|
||||
$ok = true;
|
||||
} else {
|
||||
if ((!$user->has2fa() && $user->checkPassword($VARS['password'])) || $user->checkAppPassword($VARS['password'])) {
|
||||
$ok = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($ok) {
|
||||
switch ($user->getStatus()->getString()) {
|
||||
case "LOCKED_OR_DISABLED":
|
||||
Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("account locked", false)]);
|
||||
case "TERMINATED":
|
||||
Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("account terminated", false)]);
|
||||
case "CHANGE_PASSWORD":
|
||||
Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("password expired", false)]);
|
||||
case "NORMAL":
|
||||
Log::insert(LogType::API_LOGIN_OK, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
exitWithJson(["status" => "OK"]);
|
||||
case "ALERT_ON_ACCESS":
|
||||
$user->sendAlertEmail();
|
||||
Log::insert(LogType::API_LOGIN_OK, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
exitWithJson(["status" => "OK", "alert" => true]);
|
||||
default:
|
||||
Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("account state error", false)]);
|
||||
}
|
||||
} else {
|
||||
Log::insert(LogType::API_LOGIN_FAILED, null, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]);
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
exitWithJson(["status" => "OK", "mobile" => $SETTINGS['mobile_enabled']]);
|
@ -1,15 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (empty($VARS['username']) || empty($VARS['code'])) {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
$code = strtoupper($VARS['code']);
|
||||
$user_key_valid = $database->has('mobile_codes', ['[>]accounts' => ['uid' => 'uid']], ["AND" => ['mobile_codes.code' => $code, 'accounts.username' => strtolower($VARS['username'])]]);
|
||||
exitWithJson(["status" => "OK", "valid" => $user_key_valid]);
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$perm = $VARS['code'];
|
||||
if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
} else if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
}
|
||||
|
||||
if (!$user->exists()) {
|
||||
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false)]);
|
||||
}
|
||||
exitWithJson(["status" => "OK", "has_permission" => $user->hasPermission($perm)]);
|
@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
sendJsonResp();
|
@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
} else {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
if (empty($VARS['id'])) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("invalid parameters", false)]));
|
||||
}
|
||||
try {
|
||||
Notifications::read($user, $VARS['id']);
|
||||
sendJsonResp();
|
||||
} catch (Exception $ex) {
|
||||
sendJsonResp($ex->getMessage(), "ERROR");
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
} else if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
}
|
||||
|
||||
sendJsonResp(null, "OK", ["exists" => $user->exists()]);
|
@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
}
|
||||
if ($user->exists()) {
|
||||
$data = $database->get("accounts", ["uid", "username", "realname (name)", "email", "phone" => ["phone1 (1)", "phone2 (2)"], 'pin'], ["uid" => $user->getUID()]);
|
||||
$data['pin'] = (is_null($data['pin']) || $data['pin'] == "" ? false : true);
|
||||
sendJsonResp(null, "OK", ["data" => $data]);
|
||||
} else {
|
||||
sendJsonResp($Strings->get("login incorrect", false), "ERROR");
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (strlen($VARS['search']) < 3) {
|
||||
exitWithJson(["status" => "OK", "result" => []]);
|
||||
}
|
||||
$data = $database->select('accounts', ['uid', 'username', 'realname (name)'], ["OR" => ['username[~]' => $VARS['search'], 'realname[~]' => $VARS['search']], "LIMIT" => 10]);
|
||||
exitWithJson(["status" => "OK", "result" => $data]);
|
@ -1,15 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$user = User::byUsername($VARS['username']);
|
||||
if ($user->check2fa($VARS['code'])) {
|
||||
sendJsonResp(null, "OK", ["valid" => true]);
|
||||
} else {
|
||||
Log::insert(LogType::API_BAD_2FA, null, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
sendJsonResp($Strings->get("2fa incorrect", false), "ERROR", ["valid" => false]);
|
||||
}
|
@ -1,267 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$APIS = [
|
||||
"ping" => [
|
||||
"load" => "ping.php",
|
||||
"vars" => [
|
||||
],
|
||||
"permission" => [
|
||||
],
|
||||
"keytype" => "NONE"
|
||||
],
|
||||
"auth" => [
|
||||
"load" => "auth.php",
|
||||
"vars" => [
|
||||
"username" => "string",
|
||||
"password" => "string",
|
||||
"apppass (optional)" => "/[0-1]/"
|
||||
],
|
||||
"keytype" => "AUTH"
|
||||
],
|
||||
"userinfo" => [
|
||||
"load" => "userinfo.php",
|
||||
"vars" => [
|
||||
"OR" => [
|
||||
"username" => "string",
|
||||
"uid" => "numeric"
|
||||
]
|
||||
],
|
||||
"keytype" => "READ"
|
||||
],
|
||||
"userexists" => [
|
||||
"load" => "userexists.php",
|
||||
"vars" => [
|
||||
"OR" => [
|
||||
"username" => "string",
|
||||
"uid" => "numeric"
|
||||
]
|
||||
],
|
||||
"keytype" => "AUTH"
|
||||
],
|
||||
"hastotp" => [
|
||||
"load" => "hastotp.php",
|
||||
"vars" => [
|
||||
"username" => "string"
|
||||
],
|
||||
"keytype" => "AUTH"
|
||||
],
|
||||
"verifytotp" => [
|
||||
"load" => "verifytotp.php",
|
||||
"vars" => [
|
||||
"username" => "string",
|
||||
"code" => "string"
|
||||
],
|
||||
"keytype" => "AUTH"
|
||||
],
|
||||
"acctstatus" => [
|
||||
"load" => "acctstatus.php",
|
||||
"vars" => [
|
||||
"username" => "string"
|
||||
],
|
||||
"keytype" => "AUTH"
|
||||
],
|
||||
"login" => [
|
||||
"load" => "login.php",
|
||||
"vars" => [
|
||||
"username" => "string",
|
||||
"password" => "string",
|
||||
"apppass (optional)" => "/[0-1]/"
|
||||
],
|
||||
"keytype" => "AUTH"
|
||||
],
|
||||
"ismanagerof" => [
|
||||
"load" => "ismanagerof.php",
|
||||
"vars" => [
|
||||
"manager" => "string",
|
||||
"employee" => "string",
|
||||
"uid (optional)" => "numeric"
|
||||
],
|
||||
"keytype" => "READ"
|
||||
],
|
||||
"getmanaged" => [
|
||||
"load" => "getmanaged.php",
|
||||
"vars" => [
|
||||
"OR" => [
|
||||
"username" => "string",
|
||||
"uid" => "numeric"
|
||||
],
|
||||
"get (optional)" => "string"
|
||||
],
|
||||
"keytype" => "READ"
|
||||
],
|
||||
"getmanagers" => [
|
||||
"load" => "getmanagers.php",
|
||||
"vars" => [
|
||||
"OR" => [
|
||||
"username" => "string",
|
||||
"uid" => "numeric"
|
||||
]
|
||||
],
|
||||
"keytype" => "READ"
|
||||
],
|
||||
"usersearch" => [
|
||||
"load" => "usersearch.php",
|
||||
"vars" => [
|
||||
"search" => "string"
|
||||
],
|
||||
"keytype" => "READ"
|
||||
],
|
||||
"permission" => [
|
||||
"load" => "permission.php",
|
||||
"vars" => [
|
||||
"OR" => [
|
||||
"username" => "string",
|
||||
"uid" => "numeric"
|
||||
],
|
||||
"code" => "string"
|
||||
],
|
||||
"keytype" => "READ"
|
||||
],
|
||||
"mobileenabled" => [
|
||||
"load" => "mobileenabled.php",
|
||||
"keytype" => "NONE"
|
||||
],
|
||||
"mobilevalid" => [
|
||||
"load" => "mobilevalid.php",
|
||||
"vars" => [
|
||||
"username" => "string",
|
||||
"code" => "string"
|
||||
],
|
||||
"keytype" => "AUTH"
|
||||
],
|
||||
"alertemail" => [
|
||||
"load" => "alertemail.php",
|
||||
"vars" => [
|
||||
"username" => "string",
|
||||
"appname (optional)" => "string"
|
||||
],
|
||||
"keytype" => "FULL"
|
||||
],
|
||||
"codelogin" => [
|
||||
"load" => "codelogin.php",
|
||||
"vars" => [
|
||||
"code" => "string"
|
||||
],
|
||||
"keytype" => "AUTH"
|
||||
],
|
||||
"listapps" => [
|
||||
"load" => "listapps.php",
|
||||
"keytype" => "NONE"
|
||||
],
|
||||
"getusersbygroup" => [
|
||||
"load" => "getusersbygroup.php",
|
||||
"vars" => [
|
||||
"gid" => "numeric",
|
||||
"get (optional)" => "string"
|
||||
],
|
||||
"keytype" => "READ"
|
||||
],
|
||||
"getgroupsbyuser" => [
|
||||
"load" => "getgroupsbyuser.php",
|
||||
"vars" => [
|
||||
"OR" => [
|
||||
"uid" => "numeric",
|
||||
"username" => "string"
|
||||
]
|
||||
],
|
||||
"keytype" => "READ"
|
||||
],
|
||||
"getgroups" => [
|
||||
"load" => "getgroups.php",
|
||||
"keytype" => "READ"
|
||||
],
|
||||
"groupsearch" => [
|
||||
"load" => "groupsearch.php",
|
||||
"vars" => [
|
||||
"search" => "string"
|
||||
],
|
||||
"keytype" => "READ"
|
||||
],
|
||||
"checkpin" => [
|
||||
"load" => "checkpin.php",
|
||||
"vars" => [
|
||||
"pin" => "string",
|
||||
"OR" => [
|
||||
"uid" => "numeric",
|
||||
"username" => "string"
|
||||
]
|
||||
],
|
||||
"keytype" => "AUTH"
|
||||
],
|
||||
"getnotifications" => [
|
||||
"load" => "getnotifications.php",
|
||||
"vars" => [
|
||||
"OR" => [
|
||||
"uid" => "numeric",
|
||||
"username" => "string"
|
||||
]
|
||||
],
|
||||
"keytype" => "READ"
|
||||
],
|
||||
"readnotification" => [
|
||||
"load" => "readnotification.php",
|
||||
"vars" => [
|
||||
"OR" => [
|
||||
"uid" => "numeric",
|
||||
"username" => "string"
|
||||
],
|
||||
"id" => "numeric"
|
||||
],
|
||||
"keytype" => "FULL"
|
||||
],
|
||||
"addnotification" => [
|
||||
"load" => "addnotification.php",
|
||||
"vars" => [
|
||||
"OR" => [
|
||||
"uid" => "numeric",
|
||||
"username" => "string"
|
||||
],
|
||||
"title" => "string",
|
||||
"content" => "string",
|
||||
"timestamp (optional)" => "string",
|
||||
"url (optional)" => "string",
|
||||
"sensitive (optional)" => "string"
|
||||
],
|
||||
"keytype" => "FULL"
|
||||
],
|
||||
"deletenotification" => [
|
||||
"load" => "deletenotification.php",
|
||||
"vars" => [
|
||||
"OR" => [
|
||||
"uid" => "numeric",
|
||||
"username" => "string"
|
||||
],
|
||||
"id" => "numeric"
|
||||
],
|
||||
"keytype" => "FULL"
|
||||
],
|
||||
"getloginkey" => [
|
||||
"load" => "getloginkey.php",
|
||||
"vars" => [
|
||||
"appname" => "string",
|
||||
"appicon (optional)" => "string"
|
||||
],
|
||||
"keytype" => "AUTH"
|
||||
],
|
||||
"checkloginkey" => [
|
||||
"load" => "checkloginkey.php",
|
||||
"vars" => [
|
||||
"code" => "string"
|
||||
],
|
||||
"keytype" => "AUTH"
|
||||
],
|
||||
"addapppassword" => [
|
||||
"load" => "addapppassword.php",
|
||||
"vars" => [
|
||||
"desc" => "string",
|
||||
"username" => "string"
|
||||
],
|
||||
"keytype" => "FULL"
|
||||
],
|
||||
];
|
@ -1,164 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Build and send a simple JSON response.
|
||||
* @param string $msg A message
|
||||
* @param string $status "OK" or "ERROR"
|
||||
* @param array $data More JSON data
|
||||
*/
|
||||
function sendJsonResp(string $msg = null, string $status = "OK", array $data = null) {
|
||||
$resp = [];
|
||||
if (!is_null($data)) {
|
||||
$resp = $data;
|
||||
}
|
||||
if (!is_null($msg)) {
|
||||
$resp["msg"] = $msg;
|
||||
}
|
||||
$resp["status"] = $status;
|
||||
header("Content-Type: application/json");
|
||||
exit(json_encode($resp));
|
||||
}
|
||||
|
||||
function exitWithJson(array $json) {
|
||||
header("Content-Type: application/json");
|
||||
exit(json_encode($json));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the API key with most of the characters replaced with *s.
|
||||
* @global string $key
|
||||
* @return string
|
||||
*/
|
||||
function getCensoredKey() {
|
||||
global $key;
|
||||
$resp = $key;
|
||||
if (strlen($key) > 5) {
|
||||
for ($i = 2; $i < strlen($key) - 2; $i++) {
|
||||
$resp[$i] = "*";
|
||||
}
|
||||
}
|
||||
return $resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the request is allowed
|
||||
* @global type $VARS
|
||||
* @global type $database
|
||||
* @return bool true if the request should continue, false if the request is bad
|
||||
*/
|
||||
function authenticate(): bool {
|
||||
global $VARS, $database;
|
||||
if (empty($VARS['key'])) {
|
||||
return false;
|
||||
} else {
|
||||
$key = $VARS['key'];
|
||||
if ($database->has('apikeys', ['key' => $key]) !== TRUE) {
|
||||
engageRateLimit();
|
||||
http_response_code(403);
|
||||
Log::insert(LogType::API_BAD_KEY, null, "Key: " . $key);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function checkVars($vars, $or = false) {
|
||||
global $VARS;
|
||||
$ok = [];
|
||||
foreach ($vars as $key => $val) {
|
||||
if (strpos($key, "OR") === 0) {
|
||||
checkVars($vars[$key], true);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only check type of optional variables if they're set, and don't
|
||||
// mark them as bad if they're not set
|
||||
if (strpos($key, " (optional)") !== false) {
|
||||
$key = str_replace(" (optional)", "", $key);
|
||||
if (empty($VARS[$key])) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (empty($VARS[$key])) {
|
||||
$ok[$key] = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (strpos($val, "/") === 0) {
|
||||
// regex
|
||||
$ok[$key] = preg_match($val, $VARS[$key]) === 1;
|
||||
} else {
|
||||
$checkmethod = "is_$val";
|
||||
$ok[$key] = !($checkmethod($VARS[$key]) !== true);
|
||||
}
|
||||
}
|
||||
if ($or) {
|
||||
$success = false;
|
||||
$bad = "";
|
||||
foreach ($ok as $k => $v) {
|
||||
if ($v) {
|
||||
$success = true;
|
||||
break;
|
||||
} else {
|
||||
$bad = $k;
|
||||
}
|
||||
}
|
||||
if (!$success) {
|
||||
http_response_code(400);
|
||||
die("400 Bad request: variable $bad is missing or invalid");
|
||||
}
|
||||
} else {
|
||||
foreach ($ok as $key => $bool) {
|
||||
if (!$bool) {
|
||||
http_response_code(400);
|
||||
die("400 Bad request: variable $key is missing or invalid");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the client API key is allowed to access API functions that require the
|
||||
* specified API key type.
|
||||
* @global type $VARS
|
||||
* @global type $database
|
||||
* @param string $type The required key type: "NONE", "AUTH", "READ", or "FULL"
|
||||
* @return bool
|
||||
*/
|
||||
function checkkeytype(string $type): bool {
|
||||
global $VARS, $database;
|
||||
if (empty($VARS['key'])) {
|
||||
return false;
|
||||
} else {
|
||||
$key = $VARS['key'];
|
||||
$keytype = $database->get('apikeys', 'type', ['key' => $key]);
|
||||
$allowedtypes = [];
|
||||
switch ($type) {
|
||||
case "NONE":
|
||||
$allowedtypes = ["NONE", "AUTH", "READ", "FULL"];
|
||||
break;
|
||||
case "AUTH":
|
||||
$allowedtypes = ["AUTH", "READ", "FULL"];
|
||||
break;
|
||||
case "READ":
|
||||
$allowedtypes = ["READ", "FULL"];
|
||||
break;
|
||||
case "FULL":
|
||||
$allowedtypes = ["FULL"];
|
||||
}
|
||||
if (!in_array($type, $allowedtypes)) {
|
||||
http_response_code(403);
|
||||
Log::insert(LogType::API_BAD_KEY, null, "Key: " . $key);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
require __DIR__ . '/../required.php';
|
||||
require __DIR__ . '/functions.php';
|
||||
require __DIR__ . '/apisettings.php';
|
||||
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
|
||||
$VARS = $_GET;
|
||||
if ($_SERVER['REQUEST_METHOD'] != "GET") {
|
||||
$VARS = array_merge($VARS, $_POST);
|
||||
}
|
||||
|
||||
$requestbody = file_get_contents('php://input');
|
||||
$requestjson = json_decode($requestbody, TRUE);
|
||||
if (json_last_error() == JSON_ERROR_NONE) {
|
||||
$VARS = array_merge($VARS, $requestjson);
|
||||
}
|
||||
|
||||
// If we're not using the old api.php file, allow more flexible requests
|
||||
if (strpos($_SERVER['REQUEST_URI'], "/api.php") === FALSE) {
|
||||
$route = explode("/", substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], "api/") + 4));
|
||||
|
||||
if (count($route) > 1) {
|
||||
$VARS["action"] = $route[0];
|
||||
}
|
||||
if (count($route) >= 2 && strpos($route[1], "?") !== 0) {
|
||||
$VARS["key"] = $route[1];
|
||||
|
||||
for ($i = 2; $i < count($route); $i++) {
|
||||
$key = explode("=", $route[$i], 2)[0];
|
||||
$val = explode("=", $route[$i], 2)[1];
|
||||
$VARS[$key] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
if (strpos($route[count($route) - 1], "?") === 0) {
|
||||
$morevars = explode("&", substr($route[count($route) - 1], 1));
|
||||
foreach ($morevars as $var) {
|
||||
$key = explode("=", $var, 2)[0];
|
||||
$val = explode("=", $var, 2)[1];
|
||||
$VARS[$key] = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!authenticate()) {
|
||||
http_response_code(403);
|
||||
die("403 Unauthorized");
|
||||
}
|
||||
|
||||
if (empty($VARS['action'])) {
|
||||
http_response_code(404);
|
||||
die("404 No action specified");
|
||||
}
|
||||
|
||||
if (!isset($APIS[$VARS['action']])) {
|
||||
http_response_code(404);
|
||||
die("404 Action not defined");
|
||||
}
|
||||
|
||||
$APIACTION = $APIS[$VARS["action"]];
|
||||
|
||||
if (!file_exists(__DIR__ . "/actions/" . $APIACTION["load"])) {
|
||||
http_response_code(404);
|
||||
die("404 Action not found");
|
||||
}
|
||||
|
||||
if (!empty($APIACTION["vars"])) {
|
||||
checkVars($APIACTION["vars"]);
|
||||
}
|
||||
|
||||
// Assume we need full API access
|
||||
if (empty($APIACTION["keytype"])) {
|
||||
$APIACTION["keytype"] = "FULL";
|
||||
}
|
||||
|
||||
if (!checkkeytype($APIACTION["keytype"])) {
|
||||
die("403 Unauthorized");
|
||||
}
|
||||
|
||||
require_once __DIR__ . "/actions/" . $APIACTION["load"];
|
222
app.php
222
app.php
@ -1,222 +0,0 @@
|
||||
<?php
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
require_once __DIR__ . "/required.php";
|
||||
|
||||
// If the SVG/JavaScript version of FontAwesome is needed
|
||||
// Increases overhead by a notable amount
|
||||
define("FONTAWESOME_USEJS", true);
|
||||
|
||||
if ($_SESSION['loggedin'] != true) {
|
||||
header('Location: index.php');
|
||||
die("Session expired. Log in again to continue.");
|
||||
}
|
||||
|
||||
require_once __DIR__ . "/pages.php";
|
||||
|
||||
$pageid = "home";
|
||||
if (!empty($_GET['page'])) {
|
||||
$pg = strtolower($_GET['page']);
|
||||
$pg = preg_replace('/[^0-9a-z_]/', "", $pg);
|
||||
if (array_key_exists($pg, PAGES) && file_exists(__DIR__ . "/pages/" . $pg . ".php")) {
|
||||
$pageid = $pg;
|
||||
} else {
|
||||
$pageid = "404";
|
||||
}
|
||||
}
|
||||
|
||||
header("Link: <static/img/logo.svg>; rel=preload; as=image", false);
|
||||
header("Link: <static/fonts/Roboto.css>; rel=preload; as=style", false);
|
||||
header("Link: <static/css/bootstrap.min.css>; rel=preload; as=style", false);
|
||||
header("Link: <static/css/material-color/material-color.min.css>; rel=preload; as=style", false);
|
||||
header("Link: <static/css/app.css>; rel=preload; as=style", false);
|
||||
if (FONTAWESOME_USEJS) {
|
||||
header("Link: <static/css/svg-with-js.min.css>; rel=preload; as=style", false);
|
||||
header("Link: <static/js/fontawesome-all.min.js>; rel=preload; as=script", false);
|
||||
} else {
|
||||
header("Link: <static/css/fontawesome-all.min.css>; rel=preload; as=style", false);
|
||||
}
|
||||
header("Link: <static/js/jquery-3.3.1.min.js>; rel=preload; as=script", false);
|
||||
header("Link: <static/js/bootstrap.bundle.min.js>; rel=preload; as=script", false);
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title><?php echo $SETTINGS['site_title']; ?></title>
|
||||
|
||||
<link rel="icon" href="static/img/logo.svg" type="image/svg+xml">
|
||||
|
||||
<link href="static/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="static/css/material-color/material-color.min.css" rel="stylesheet">
|
||||
<link href="static/css/app.css" rel="stylesheet">
|
||||
<?php
|
||||
if (FONTAWESOME_USEJS) {
|
||||
?>
|
||||
<link href="static/css/svg-with-js.min.css" rel="stylesheet">
|
||||
<script nonce="<?php echo $SECURE_NONCE; ?>">
|
||||
FontAwesomeConfig = {autoAddCss: false}
|
||||
</script>
|
||||
<script src="static/js/fontawesome-all.min.js"></script>
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
<link href="static/css/fontawesome-all.min.css" rel="stylesheet">
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<?php
|
||||
// custom page styles
|
||||
if (isset(PAGES[$pageid]['styles'])) {
|
||||
foreach (PAGES[$pageid]['styles'] as $style) {
|
||||
echo "<link href=\"$style\" rel=\"stylesheet\">\n";
|
||||
header("Link: <$style>; rel=preload; as=style", false);
|
||||
}
|
||||
}
|
||||
?>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<?php
|
||||
// Alert messages
|
||||
if (!empty($_GET['msg'])) {
|
||||
if (array_key_exists($_GET['msg'], MESSAGES)) {
|
||||
// optional string generation argument
|
||||
if (empty($_GET['arg'])) {
|
||||
$alertmsg = $Strings->get(MESSAGES[$_GET['msg']]['string'], false);
|
||||
} else {
|
||||
$alertmsg = $Strings->build(MESSAGES[$_GET['msg']]['string'], ["arg" => strip_tags($_GET['arg'])], false);
|
||||
}
|
||||
$alerttype = MESSAGES[$_GET['msg']]['type'];
|
||||
$alerticon = "square-o";
|
||||
switch (MESSAGES[$_GET['msg']]['type']) {
|
||||
case "danger":
|
||||
$alerticon = "times";
|
||||
break;
|
||||
case "warning":
|
||||
$alerticon = "exclamation-triangle";
|
||||
break;
|
||||
case "info":
|
||||
$alerticon = "info-circle";
|
||||
break;
|
||||
case "success":
|
||||
$alerticon = "check";
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// We don't have a message for this, so just assume an error and escape stuff.
|
||||
$alertmsg = htmlentities($Strings->get($_GET['msg'], false));
|
||||
$alerticon = "times";
|
||||
$alerttype = "danger";
|
||||
}
|
||||
echo <<<END
|
||||
<div class="row justify-content-center" id="msg-alert-box">
|
||||
<div class="col-11 col-sm-6 col-md-5 col-lg-4 col-xl-4">
|
||||
<div class="alert alert-dismissible alert-$alerttype mt-2 p-0 border-0 shadow">
|
||||
<div class="p-2 pl-3">
|
||||
<button type="button" class="close">×</button>
|
||||
<i class="fas fa-$alerticon"></i> $alertmsg
|
||||
</div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar bg-$alerttype w-0" id="msg-alert-timeout-bar"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
END;
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
// Adjust as needed
|
||||
$navbar_breakpoint = "md";
|
||||
|
||||
// For mobile app
|
||||
echo "<script nonce=\"$SECURE_NONCE\">var navbar_breakpoint = \"$navbar_breakpoint\";</script>"
|
||||
?>
|
||||
<nav class="navbar navbar-expand-<?php echo $navbar_breakpoint; ?> navbar-light bg-orange fixed-top">
|
||||
<button class="navbar-toggler my-0 py-0" type="button" data-toggle="collapse" data-target="#navbar-collapse" aria-controls="navbar-collapse" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<!--<i class="fas fa-bars"></i>-->
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<a class="navbar-brand py-0 mr-auto" href="app.php">
|
||||
<img src="static/img/logo.svg" alt="" class="d-none d-<?php echo $navbar_breakpoint; ?>-inline brand-img py-0" />
|
||||
<?php echo $SETTINGS['site_title']; ?>
|
||||
</a>
|
||||
|
||||
<div class="collapse navbar-collapse py-0" id="navbar-collapse">
|
||||
<div class="navbar-nav mr-auto py-0">
|
||||
<?php
|
||||
$curpagefound = false;
|
||||
foreach (PAGES as $id => $pg) {
|
||||
if (isset($pg['navbar']) && $pg['navbar'] === TRUE) {
|
||||
if ($pageid == $id) {
|
||||
$curpagefound = true;
|
||||
?>
|
||||
<span class="nav-item py-<?php echo $navbar_breakpoint; ?>-0 active">
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
<span class="nav-item py-<?php echo $navbar_breakpoint; ?>-0">
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<a class="nav-link py-<?php echo $navbar_breakpoint; ?>-0" href="app.php?page=<?php echo $id; ?>">
|
||||
<?php
|
||||
if (isset($pg['icon'])) {
|
||||
?><i class="<?php echo $pg['icon']; ?> fa-fw"></i> <?php
|
||||
}
|
||||
$Strings->get($pg['title']);
|
||||
?>
|
||||
</a>
|
||||
</span>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<div class="navbar-nav ml-auto py-0" id="navbar-right">
|
||||
<span class="nav-item py-<?php echo $navbar_breakpoint; ?>-0">
|
||||
<a class="nav-link py-<?php echo $navbar_breakpoint; ?>-0" href="app.php">
|
||||
<i class="fas fa-user fa-fw"></i><span> <?php echo $_SESSION['realname'] ?></span>
|
||||
</a>
|
||||
</span>
|
||||
<span class="nav-item mr-auto py-<?php echo $navbar_breakpoint; ?>-0">
|
||||
<a class="nav-link py-<?php echo $navbar_breakpoint; ?>-0" href="action.php?action=signout">
|
||||
<i class="fas fa-sign-out-alt fa-fw"></i><span> <?php $Strings->get("sign out") ?></span>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container" id="main-content">
|
||||
<div>
|
||||
<?php
|
||||
include_once __DIR__ . '/pages/' . $pageid . ".php";
|
||||
?>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<?php echo $SETTINGS['footer_text']; ?><br />
|
||||
Copyright © <?php echo date('Y'); ?> <?php echo $SETTINGS['copyright']; ?>
|
||||
</div>
|
||||
</div>
|
||||
<script src="static/js/jquery-3.3.1.min.js"></script>
|
||||
<script src="static/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="static/js/app.js"></script>
|
||||
<?php
|
||||
// custom page scripts
|
||||
if (isset(PAGES[$pageid]['scripts'])) {
|
||||
foreach (PAGES[$pageid]['scripts'] as $script) {
|
||||
echo "<script src=\"$script\"></script>\n";
|
||||
header("Link: <$script>; rel=preload; as=script", false);
|
||||
}
|
||||
}
|
||||
?>
|
||||
</body>
|
||||
</html>
|
17
apps/2fa_qrcode.php
Normal file
17
apps/2fa_qrcode.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
dieifnotloggedin();
|
||||
|
||||
// extra login utils
|
||||
require_once __DIR__ . "/../lib/login.php";
|
||||
|
||||
$APPS["setup_2fa"]["title"] = lang("setup 2fa", false);
|
||||
$APPS["setup_2fa"]["icon"] = "lock";
|
||||
if (userHasTOTP($_SESSION['username'])) {
|
||||
$APPS["setup_2fa"]["content"] = '<a href="action.php?action=rm2fa&source=security" class="btn btn-warning">'
|
||||
. lang("remove 2fa", false) . '</a>';
|
||||
} else {
|
||||
$APPS["setup_2fa"]["content"] = '<div class="alert alert-info"><i class="fa fa-info-circle"></i> ' . lang("2fa explained", false) . '</div>'
|
||||
. '<button class="btn btn-success">'
|
||||
. lang("enable 2fa", false) . '</button>';
|
||||
}
|
9
apps/404_error.php
Normal file
9
apps/404_error.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
dieifnotloggedin();
|
||||
|
||||
$APPS["404_error"]["title"] = lang("404 error", false);
|
||||
$APPS["404_error"]["icon"] = "times";
|
||||
$APPS["404_error"]["type"] = "warning";
|
||||
$APPS["404_error"]["content"] = "<h4>" . lang("page not found", false) . "</h4>";
|
||||
?>
|
19
apps/account_security.php
Normal file
19
apps/account_security.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
dieifnotloggedin();
|
||||
addMultiLangStrings(["en_us" => [
|
||||
"manage account security" => "Manage account security",
|
||||
"manage security description" => "Review security features or change your password."
|
||||
]
|
||||
]);
|
||||
$APPS["account_security"]["i18n"] = TRUE;
|
||||
$APPS["account_security"]["title"] = "account security";
|
||||
$APPS["account_security"]["icon"] = "lock";
|
||||
$content = "<p>"
|
||||
. lang("manage security description", false)
|
||||
. '</p> '
|
||||
. '<a href="home.php?page=security" class="btn btn-primary btn-block">'
|
||||
. lang("manage account security", false)
|
||||
. '</a>';
|
||||
$APPS["account_security"]["content"] = $content;
|
||||
?>
|
17
apps/change_password.php
Normal file
17
apps/change_password.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
dieifnotloggedin();
|
||||
|
||||
$APPS["change_password"]["title"] = "Change Password";
|
||||
$APPS["change_password"]["icon"] = "key";
|
||||
$APPS["change_password"]["content"] = <<<CONTENTEND
|
||||
<form action="action.php" method="POST">
|
||||
<input type="password" class="form-control" name="oldpass" placeholder="Current password" />
|
||||
<input type="password" class="form-control" name="newpass" placeholder="New password" />
|
||||
<input type="password" class="form-control" name="conpass" placeholder="New password (again)" />
|
||||
<input type="hidden" name="action" value="chpasswd" />
|
||||
<input type="hidden" name="source" value="security" />
|
||||
<br />
|
||||
<button type="submit" class="btn btn-success btn-sm btn-block">Change Password</button>
|
||||
</form>
|
||||
CONTENTEND;
|
14
apps/inventory_link.php
Normal file
14
apps/inventory_link.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
dieifnotloggedin();
|
||||
addMultiLangStrings(["en_us" => [
|
||||
"inventory" => "Inventory",
|
||||
"open inventory system" => "Open the inventory system"
|
||||
]
|
||||
]);
|
||||
$APPS["inventory_link"]["i18n"] = TRUE;
|
||||
$APPS["inventory_link"]["title"] = "inventory";
|
||||
$APPS["inventory_link"]["icon"] = "cubes";
|
||||
$content = "<p>" . lang("open inventory system", false) . '</p><a href="' . INVENTORY_HOME . '" class="btn btn-primary btn-block">' . lang("open app", false) . ' <i class="fa fa-external-link-square"></i></a>';
|
||||
$APPS["inventory_link"]["content"] = $content;
|
||||
?>
|
30
apps/sample_app.php
Normal file
30
apps/sample_app.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
dieifnotloggedin();
|
||||
|
||||
// Additional i18n strings
|
||||
addMultiLangStrings(["en_us" => [
|
||||
"sample app" => "Sample Application",
|
||||
]
|
||||
]);
|
||||
// Set to true to automatically parse the app title as a language string.
|
||||
$APPS["sample_app"]["i18n"] = TRUE;
|
||||
// App title.
|
||||
$APPS["sample_app"]["title"] = "sample app";
|
||||
// App icon, from FontAwesome.
|
||||
$APPS["sample_app"]["icon"] = "rocket";
|
||||
// App content.
|
||||
$APPS["sample_app"]["content"] = <<<'CONTENTEND'
|
||||
<div class="list-group">
|
||||
<div class="list-group-item">
|
||||
Item 1
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
Item 2
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
Item 3
|
||||
</div>
|
||||
</div>
|
||||
CONTENTEND;
|
||||
?>
|
43
apps/setup_2fa.php
Normal file
43
apps/setup_2fa.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
dieifnotloggedin();
|
||||
|
||||
use OTPHP\Factory;
|
||||
use Endroid\QrCode\QrCode;
|
||||
|
||||
// extra login utils
|
||||
require_once __DIR__ . "/../lib/login.php";
|
||||
|
||||
$APPS["setup_2fa"]["title"] = lang("setup 2fa", false);
|
||||
$APPS["setup_2fa"]["icon"] = "lock";
|
||||
if (userHasTOTP($_SESSION['username'])) {
|
||||
$APPS["setup_2fa"]["content"] = '<div class="alert alert-info"><i class="fa fa-info-circle"></i> ' . lang("2fa active", false) . '</div>'
|
||||
. '<a href="action.php?action=rm2fa&source=security" class="btn btn-warning btn-sm btn-block">'
|
||||
. lang("remove 2fa", false) . '</a>';
|
||||
} else if ($_GET['2fa'] == "generate") {
|
||||
$codeuri = newTOTP($_SESSION['username']);
|
||||
$qrCode = new QrCode($codeuri);
|
||||
$qrCode->setSize(200);
|
||||
$qrCode->setErrorCorrection("H");
|
||||
$qrcode = $qrCode->getDataUri();
|
||||
$totp = Factory::loadFromProvisioningUri($codeuri);
|
||||
$codesecret = $totp->getSecret();
|
||||
$chunk_secret = trim(chunk_split($codesecret, 8, ' '));
|
||||
$APPS["setup_2fa"]["content"] = '<div class="alert alert-info"><i class="fa fa-info-circle"></i> ' . lang("scan 2fa qrcode", false) . '</div>' . <<<END
|
||||
<img src="$qrcode" class="img-responsive qrcode" />
|
||||
<div class="well well-sm" style="text-align: center; font-size: 110%; font-family: monospace;">$chunk_secret</div>
|
||||
<form action="action.php" method="POST">
|
||||
<input type="hidden" name="action" value="add2fa" />
|
||||
<input type="hidden" name="source" value="security" />
|
||||
<input type="hidden" name="secret" value="$codesecret" />
|
||||
<button type="submit" class="btn btn-success btn-sm btn-block">
|
||||
END
|
||||
. lang("confirm 2fa", false) . <<<END
|
||||
</button>
|
||||
</form>
|
||||
END;
|
||||
} else {
|
||||
$APPS["setup_2fa"]["content"] = '<div class="alert alert-info"><i class="fa fa-info-circle"></i> ' . lang("2fa explained", false) . '</div>'
|
||||
. '<a class="btn btn-success btn-sm btn-block" href="home.php?page=security&2fa=generate">'
|
||||
. lang("enable 2fa", false) . '</a>';
|
||||
}
|
53
apps/taskfloor_messages.php
Normal file
53
apps/taskfloor_messages.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
dieifnotloggedin();
|
||||
addMultiLangStrings(["en_us" => [
|
||||
"messages" => "Messages",
|
||||
"no messages" => "No messages found."
|
||||
]
|
||||
]);
|
||||
$APPS["taskfloor_messages"]["i18n"] = TRUE;
|
||||
$APPS["taskfloor_messages"]["title"] = "messages";
|
||||
$APPS["taskfloor_messages"]["icon"] = "comments";
|
||||
try {
|
||||
$client = new GuzzleHttp\Client();
|
||||
|
||||
$response = $client->request('POST', TASKFLOOR_API, ['form_params' => [
|
||||
'action' => "getmsgs",
|
||||
'username' => $_SESSION['username'],
|
||||
'password' => $_SESSION['password'],
|
||||
'max' => 5
|
||||
]]);
|
||||
|
||||
$resp = json_decode($response->getBody(), TRUE);
|
||||
if ($resp['status'] == "OK") {
|
||||
if (count($resp['messages']) > 0) {
|
||||
$content = '<div class="list-group">';
|
||||
foreach ($resp['messages'] as $msg) {
|
||||
$content .= '<div class="list-group-item">';
|
||||
$content .= $msg['text'];
|
||||
$fromuser = $msg['from']['username'];
|
||||
$fromname = $msg['from']['name'];
|
||||
$touser = $msg['to']['username'];
|
||||
$toname = $msg['to']['name'];
|
||||
$content .= <<<END
|
||||
<br />
|
||||
<span class="small">
|
||||
<span data-toggle="tooltip" title="$fromuser">$fromname</span>
|
||||
<i class="fa fa-caret-right"></i>
|
||||
<span data-toggle="tooltip" title="$touser">$toname</span>
|
||||
</span>
|
||||
END;
|
||||
$content .= '</div>';
|
||||
}
|
||||
$content .= "</div>";
|
||||
} else {
|
||||
$content = "<div class=\"alert alert-info\">" . lang("no messages", false) . "</div>";
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$content = "<div class=\"alert alert-danger\">" . lang("error loading widget", false) . " " . $e->getMessage() . "</div>";
|
||||
}
|
||||
$content .= '<a href="' . TASKFLOOR_HOME . '" class="btn btn-primary btn-block">' . lang("open app", false) . ' <i class="fa fa-external-link-square"></i></a>';
|
||||
$APPS["taskfloor_messages"]["content"] = $content;
|
||||
?>
|
41
apps/taskfloor_tasks.php
Normal file
41
apps/taskfloor_tasks.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
dieifnotloggedin();
|
||||
addMultiLangStrings(["en_us" => [
|
||||
"tasks" => "Tasks",
|
||||
"no tasks found" => "No tasks found."
|
||||
]
|
||||
]);
|
||||
$APPS["taskfloor_tasks"]["i18n"] = TRUE;
|
||||
$APPS["taskfloor_tasks"]["title"] = "tasks";
|
||||
$APPS["taskfloor_tasks"]["icon"] = "tasks";
|
||||
try {
|
||||
$client = new GuzzleHttp\Client();
|
||||
|
||||
$response = $client->request('POST', TASKFLOOR_API, ['form_params' => [
|
||||
'action' => "gettasks",
|
||||
'username' => $_SESSION['username'],
|
||||
'password' => $_SESSION['password'],
|
||||
'max' => 5
|
||||
]]);
|
||||
|
||||
$resp = json_decode($response->getBody(), TRUE);
|
||||
if ($resp['status'] == "OK") {
|
||||
if (count($resp['tasks']) > 0) {
|
||||
$content = '<div class="list-group">';
|
||||
foreach ($resp['tasks'] as $task) {
|
||||
$content .= '<div class="list-group-item">';
|
||||
$content .= '<i class="fa fa-fw fa-' . $task['icon'] . '"></i> ' . $task['title'] . '';
|
||||
$content .= '</div>';
|
||||
}
|
||||
$content .= "</div>";
|
||||
} else {
|
||||
$content = "<div class=\"alert alert-success\">" . lang("no tasks found", false) . "</div>";
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$content = "<div class=\"alert alert-danger\">" . lang("error loading widget", false) . " " . $e->getMessage() . "</div>";
|
||||
}
|
||||
$content .= '<a href="' . TASKFLOOR_HOME . '" class="btn btn-primary btn-block">' . lang("open app", false) . ' <i class="fa fa-external-link-square"></i></a>';
|
||||
$APPS["taskfloor_tasks"]["content"] = $content;
|
||||
?>
|
@ -1,17 +1,14 @@
|
||||
{
|
||||
"name": "netsyms/accounthub",
|
||||
"description": "Single-sign-on system and dashboard for Netsyms Business Apps",
|
||||
"name": "netsyms/business-sso",
|
||||
"description": "Single-sign-on system and accounts database for Netsyms small business apps",
|
||||
"type": "project",
|
||||
"require": {
|
||||
"catfan/medoo": "^1.7",
|
||||
"guzzlehttp/guzzle": "^6.5",
|
||||
"catfan/medoo": "^1.2",
|
||||
"spomky-labs/otphp": "^8.3",
|
||||
"endroid/qr-code": "^3.2",
|
||||
"phpmailer/phpmailer": "^5.2",
|
||||
"christian-riesen/base32": "^1.3",
|
||||
"mibe/feedwriter": "^1.1"
|
||||
"endroid/qrcode": "^1.9",
|
||||
"ldaptools/ldaptools": "^0.24.0",
|
||||
"guzzlehttp/guzzle": "^6.2"
|
||||
},
|
||||
"license": "MPL-2.0",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Skylar Ittner",
|
||||
|
992
composer.lock
generated
992
composer.lock
generated
File diff suppressed because it is too large
Load Diff
BIN
database.mwb
BIN
database.mwb
Binary file not shown.
442
database.sql
442
database.sql
@ -1,442 +0,0 @@
|
||||
-- MySQL Script generated by MySQL Workbench
|
||||
-- Mon 11 Feb 2019 04:07:57 PM MST
|
||||
-- Model: New Model Version: 1.0
|
||||
-- MySQL Workbench Forward Engineering
|
||||
|
||||
SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
|
||||
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
|
||||
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL,ALLOW_INVALID_DATES';
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Schema accounthub
|
||||
-- -----------------------------------------------------
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `acctstatus`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `acctstatus` (
|
||||
`statusid` INT NOT NULL AUTO_INCREMENT,
|
||||
`statuscode` VARCHAR(45) NOT NULL,
|
||||
PRIMARY KEY (`statusid`),
|
||||
UNIQUE INDEX `statusid_UNIQUE` (`statusid` ASC),
|
||||
UNIQUE INDEX `statuscode_UNIQUE` (`statuscode` ASC))
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `accttypes`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `accttypes` (
|
||||
`typeid` INT NOT NULL AUTO_INCREMENT,
|
||||
`typecode` VARCHAR(45) NOT NULL,
|
||||
PRIMARY KEY (`typeid`),
|
||||
UNIQUE INDEX `typeid_UNIQUE` (`typeid` ASC),
|
||||
UNIQUE INDEX `typecode_UNIQUE` (`typecode` ASC))
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `accounts`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `accounts` (
|
||||
`uid` INT NOT NULL AUTO_INCREMENT,
|
||||
`username` VARCHAR(190) NOT NULL,
|
||||
`password` VARCHAR(255) NULL,
|
||||
`realname` VARCHAR(255) NOT NULL,
|
||||
`email` VARCHAR(255) NULL DEFAULT 'NOEMAIL@EXAMPLE.COM',
|
||||
`authsecret` VARCHAR(100) NULL,
|
||||
`pin` VARCHAR(10) NULL,
|
||||
`phone1` VARCHAR(45) NOT NULL,
|
||||
`phone2` VARCHAR(45) NOT NULL,
|
||||
`acctstatus` INT NOT NULL DEFAULT 0,
|
||||
`accttype` INT NOT NULL,
|
||||
`deleted` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`uid`, `username`),
|
||||
UNIQUE INDEX `userid_UNIQUE` (`uid` ASC),
|
||||
UNIQUE INDEX `username_UNIQUE` (`username` ASC),
|
||||
INDEX `fk_accounts_acctstatus_idx` (`acctstatus` ASC),
|
||||
INDEX `email_index` (`email` ASC),
|
||||
INDEX `fk_accounts_accttypes1_idx` (`accttype` ASC),
|
||||
CONSTRAINT `fk_accounts_acctstatus`
|
||||
FOREIGN KEY (`acctstatus`)
|
||||
REFERENCES `acctstatus` (`statusid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION,
|
||||
CONSTRAINT `fk_accounts_accttypes1`
|
||||
FOREIGN KEY (`accttype`)
|
||||
REFERENCES `accttypes` (`typeid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `apikeys`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `apikeys` (
|
||||
`key` VARCHAR(60) NOT NULL,
|
||||
`notes` TEXT NULL,
|
||||
`type` VARCHAR(45) NOT NULL DEFAULT 'FULL',
|
||||
PRIMARY KEY (`key`))
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `groups`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `groups` (
|
||||
`groupid` INT NOT NULL AUTO_INCREMENT,
|
||||
`groupname` VARCHAR(45) NOT NULL,
|
||||
PRIMARY KEY (`groupid`),
|
||||
UNIQUE INDEX `groupid_UNIQUE` (`groupid` ASC),
|
||||
UNIQUE INDEX `groupname_UNIQUE` (`groupname` ASC))
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `assigned_groups`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `assigned_groups` (
|
||||
`groupid` INT NOT NULL,
|
||||
`uid` INT NOT NULL,
|
||||
PRIMARY KEY (`groupid`, `uid`),
|
||||
INDEX `fk_groups_has_accounts_accounts1_idx` (`uid` ASC),
|
||||
INDEX `fk_groups_has_accounts_groups1_idx` (`groupid` ASC),
|
||||
CONSTRAINT `fk_groups_has_accounts_groups1`
|
||||
FOREIGN KEY (`groupid`)
|
||||
REFERENCES `groups` (`groupid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION,
|
||||
CONSTRAINT `fk_groups_has_accounts_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `managers`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `managers` (
|
||||
`managerid` INT NOT NULL,
|
||||
`employeeid` INT NOT NULL,
|
||||
PRIMARY KEY (`managerid`, `employeeid`),
|
||||
INDEX `fk_managers_accounts2_idx` (`employeeid` ASC),
|
||||
CONSTRAINT `fk_managers_accounts1`
|
||||
FOREIGN KEY (`managerid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION,
|
||||
CONSTRAINT `fk_managers_accounts2`
|
||||
FOREIGN KEY (`employeeid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `logtypes`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `logtypes` (
|
||||
`logtype` INT NOT NULL,
|
||||
`typename` VARCHAR(45) NULL,
|
||||
PRIMARY KEY (`logtype`),
|
||||
UNIQUE INDEX `logtype_UNIQUE` (`logtype` ASC))
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `authlog`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `authlog` (
|
||||
`logid` INT NOT NULL AUTO_INCREMENT,
|
||||
`logtime` DATETIME NOT NULL,
|
||||
`logtype` INT NOT NULL,
|
||||
`uid` INT NULL,
|
||||
`ip` VARCHAR(45) NULL,
|
||||
`otherdata` VARCHAR(255) NULL,
|
||||
PRIMARY KEY (`logid`),
|
||||
UNIQUE INDEX `logid_UNIQUE` (`logid` ASC),
|
||||
INDEX `fk_authlog_logtypes1_idx` (`logtype` ASC),
|
||||
INDEX `fk_authlog_accounts1_idx` (`uid` ASC),
|
||||
CONSTRAINT `fk_authlog_logtypes1`
|
||||
FOREIGN KEY (`logtype`)
|
||||
REFERENCES `logtypes` (`logtype`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION,
|
||||
CONSTRAINT `fk_authlog_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `permissions`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `permissions` (
|
||||
`permid` INT NOT NULL AUTO_INCREMENT,
|
||||
`permcode` VARCHAR(45) NOT NULL,
|
||||
`perminfo` VARCHAR(200) NULL,
|
||||
PRIMARY KEY (`permid`),
|
||||
UNIQUE INDEX `permid_UNIQUE` (`permid` ASC))
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `assigned_permissions`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `assigned_permissions` (
|
||||
`uid` INT NOT NULL,
|
||||
`permid` INT NOT NULL,
|
||||
PRIMARY KEY (`uid`, `permid`),
|
||||
INDEX `fk_permissions_has_accounts_accounts1_idx` (`uid` ASC),
|
||||
INDEX `fk_permissions_has_accounts_permissions1_idx` (`permid` ASC),
|
||||
CONSTRAINT `fk_permissions_has_accounts_permissions1`
|
||||
FOREIGN KEY (`permid`)
|
||||
REFERENCES `permissions` (`permid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION,
|
||||
CONSTRAINT `fk_permissions_has_accounts_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `mobile_codes`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `mobile_codes` (
|
||||
`codeid` INT NOT NULL AUTO_INCREMENT,
|
||||
`uid` INT NOT NULL,
|
||||
`code` VARCHAR(45) NOT NULL DEFAULT '',
|
||||
`description` VARCHAR(255) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (`codeid`),
|
||||
UNIQUE INDEX `codeid_UNIQUE` (`codeid` ASC),
|
||||
INDEX `fk_mobile_codes_accounts1_idx` (`uid` ASC),
|
||||
UNIQUE INDEX `code_UNIQUE` (`code` ASC),
|
||||
CONSTRAINT `fk_mobile_codes_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `rate_limit`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `rate_limit` (
|
||||
`ipaddr` VARCHAR(45) NOT NULL,
|
||||
`lastaction` DATETIME NULL,
|
||||
PRIMARY KEY (`ipaddr`))
|
||||
ENGINE = MEMORY;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `onetimekeys`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `onetimekeys` (
|
||||
`key` VARCHAR(10) NOT NULL,
|
||||
`uid` INT NOT NULL,
|
||||
`expires` DATETIME NOT NULL,
|
||||
INDEX `fk_onetimekeys_accounts1_idx` (`uid` ASC),
|
||||
PRIMARY KEY (`key`),
|
||||
UNIQUE INDEX `key_UNIQUE` (`key` ASC),
|
||||
CONSTRAINT `fk_onetimekeys_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `notifications`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `notifications` (
|
||||
`notificationid` INT NOT NULL AUTO_INCREMENT,
|
||||
`uid` INT NOT NULL,
|
||||
`timestamp` DATETIME NOT NULL,
|
||||
`title` VARCHAR(255) NOT NULL,
|
||||
`content` TINYTEXT NOT NULL,
|
||||
`url` VARCHAR(255) NOT NULL,
|
||||
`seen` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`sensitive` TINYINT(1) NOT NULL,
|
||||
`appid` VARCHAR(255) NULL,
|
||||
PRIMARY KEY (`notificationid`, `uid`),
|
||||
UNIQUE INDEX `notificationid_UNIQUE` (`notificationid` ASC),
|
||||
INDEX `fk_notifications_accounts1_idx` (`uid` ASC),
|
||||
CONSTRAINT `fk_notifications_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `userkeytypes`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `userkeytypes` (
|
||||
`typeid` INT NOT NULL,
|
||||
`typename` VARCHAR(45) NOT NULL,
|
||||
PRIMARY KEY (`typeid`, `typename`))
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `userkeys`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `userkeys` (
|
||||
`uid` INT NOT NULL,
|
||||
`key` VARCHAR(100) NOT NULL,
|
||||
`created` DATETIME NULL,
|
||||
`typeid` INT NOT NULL,
|
||||
PRIMARY KEY (`uid`),
|
||||
INDEX `fk_userkeys_userkeytypes1_idx` (`typeid` ASC),
|
||||
CONSTRAINT `fk_userkeys_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION,
|
||||
CONSTRAINT `fk_userkeys_userkeytypes1`
|
||||
FOREIGN KEY (`typeid`)
|
||||
REFERENCES `userkeytypes` (`typeid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `userloginkeys`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `userloginkeys` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT,
|
||||
`key` VARCHAR(255) NOT NULL,
|
||||
`expires` DATETIME NULL,
|
||||
`uid` INT NULL,
|
||||
`appname` VARCHAR(255) NOT NULL,
|
||||
`appicon` TINYTEXT NULL,
|
||||
PRIMARY KEY (`id`, `key`),
|
||||
UNIQUE INDEX `id_UNIQUE` (`id` ASC),
|
||||
UNIQUE INDEX `key_UNIQUE` (`key` ASC),
|
||||
INDEX `fk_userloginkeys_accounts1_idx` (`uid` ASC),
|
||||
CONSTRAINT `fk_userloginkeys_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `apppasswords`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `apppasswords` (
|
||||
`passid` INT NOT NULL AUTO_INCREMENT,
|
||||
`hash` VARCHAR(255) NOT NULL,
|
||||
`uid` INT NOT NULL,
|
||||
`description` VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY (`passid`, `uid`),
|
||||
UNIQUE INDEX `passid_UNIQUE` (`passid` ASC),
|
||||
INDEX `fk_apppasswords_accounts1_idx` (`uid` ASC),
|
||||
CONSTRAINT `fk_apppasswords_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
SET SQL_MODE=@OLD_SQL_MODE;
|
||||
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
|
||||
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Data for table `acctstatus`
|
||||
-- -----------------------------------------------------
|
||||
START TRANSACTION;
|
||||
INSERT INTO `acctstatus` (`statusid`, `statuscode`) VALUES (1, 'NORMAL');
|
||||
INSERT INTO `acctstatus` (`statusid`, `statuscode`) VALUES (2, 'LOCKED_OR_DISABLED');
|
||||
INSERT INTO `acctstatus` (`statusid`, `statuscode`) VALUES (3, 'CHANGE_PASSWORD');
|
||||
INSERT INTO `acctstatus` (`statusid`, `statuscode`) VALUES (4, 'TERMINATED');
|
||||
INSERT INTO `acctstatus` (`statusid`, `statuscode`) VALUES (5, 'ALERT_ON_ACCESS');
|
||||
|
||||
COMMIT;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Data for table `accttypes`
|
||||
-- -----------------------------------------------------
|
||||
START TRANSACTION;
|
||||
INSERT INTO `accttypes` (`typeid`, `typecode`) VALUES (1, 'LOCAL');
|
||||
INSERT INTO `accttypes` (`typeid`, `typecode`) VALUES (2, 'EXTERNAL');
|
||||
|
||||
COMMIT;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Data for table `logtypes`
|
||||
-- -----------------------------------------------------
|
||||
START TRANSACTION;
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (1, 'PORTAL_LOGIN_OK');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (2, 'PORTAL_LOGIN_FAILED');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (3, 'PASSWORD_CHANGED');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (4, 'API_LOGIN_OK');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (5, 'API_LOGIN_FAILED');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (6, 'PORTAL_BAD_AUTHCODE');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (7, 'API_BAD_AUTHCODE');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (8, 'BAD_CAPTCHA');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (9, '2FA_ADDED');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (10, '2FA_REMOVED');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (11, 'PORTAL_LOGOUT');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (12, 'API_AUTH_OK');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (13, 'API_AUTH_FAILED');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (14, 'API_BAD_KEY');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (15, 'LOG_CLEARED');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (16, 'USER_REMOVED');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (17, 'USER_ADDED');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (18, 'USER_EDITED');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (19, 'MOBILE_LOGIN_OK');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (20, 'MOBILE_LOGIN_FAILED');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (21, 'MOBILE_BAD_KEY');
|
||||
|
||||
COMMIT;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Data for table `permissions`
|
||||
-- -----------------------------------------------------
|
||||
START TRANSACTION;
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (1, 'ADMIN', 'System administrator');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (100, 'INV_VIEW', 'Access inventory system');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (101, 'INV_EDIT', 'Edit inventory system');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (200, 'TASKFLOOR', 'Access TaskFloor');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (300, 'QWIKCLOCK', 'Access QwikClock and punch in/out');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (301, 'QWIKCLOCK_MANAGE', 'Edit punches and other data for managed users');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (302, 'QWIKCLOCK_EDITSELF', 'Edit own punches and other data');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (303, 'QWIKCLOCK_ADMIN', 'Add and edit shifts and other data for all users');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (400, 'SITEWRITER', 'Manage and edit websites, messages, and analytics');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (401, 'SITEWRITER_CONTACT', 'Manage messages sent via website contact forms');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (402, 'SITEWRITER_ANALYTICS', 'View website analytics');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (403, 'SITEWRITER_EDIT', 'Edit website content');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (404, 'SITEWRITER_FILES', 'Manage and upload files');
|
||||
|
||||
COMMIT;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Data for table `userkeytypes`
|
||||
-- -----------------------------------------------------
|
||||
START TRANSACTION;
|
||||
INSERT INTO `userkeytypes` (`typeid`, `typename`) VALUES (1, 'RSSAtomFeed');
|
||||
INSERT INTO `userkeytypes` (`typeid`, `typename`) VALUES (2, 'Other');
|
||||
|
||||
COMMIT;
|
||||
|
@ -1,54 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS `onetimekeys` (
|
||||
`key` VARCHAR(10) NOT NULL,
|
||||
`uid` INT(11) NOT NULL,
|
||||
`expires` DATETIME NOT NULL,
|
||||
INDEX `fk_onetimekeys_accounts1_idx` (`uid` ASC),
|
||||
PRIMARY KEY (`key`),
|
||||
UNIQUE INDEX `key_UNIQUE` (`key` ASC),
|
||||
CONSTRAINT `fk_onetimekeys_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounthub`.`accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARACTER SET = utf8;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `notifications` (
|
||||
`notificationid` INT NOT NULL AUTO_INCREMENT,
|
||||
`uid` INT NOT NULL,
|
||||
`timestamp` DATETIME NOT NULL,
|
||||
`title` VARCHAR(255) NOT NULL,
|
||||
`content` TINYTEXT NOT NULL,
|
||||
`url` VARCHAR(255) NOT NULL,
|
||||
`seen` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`sensitive` TINYINT(1) NOT NULL,
|
||||
`appid` VARCHAR(255) NULL,
|
||||
PRIMARY KEY (`notificationid`, `uid`),
|
||||
UNIQUE INDEX `notificationid_UNIQUE` (`notificationid` ASC),
|
||||
INDEX `fk_notifications_accounts1_idx` (`uid` ASC),
|
||||
CONSTRAINT `fk_notifications_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARACTER SET = utf8;
|
||||
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
ALTER TABLE `groups`
|
||||
CHANGE COLUMN `groupid` `groupid` INT(11) NOT NULL AUTO_INCREMENT;
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
ALTER TABLE `accounts`
|
||||
ADD COLUMN `pin` VARCHAR(10) NULL DEFAULT NULL AFTER `authsecret`;
|
||||
|
||||
ALTER TABLE `mobile_codes`
|
||||
CHANGE COLUMN `code` `code` VARCHAR(45) NOT NULL DEFAULT '',
|
||||
ADD COLUMN `description` VARCHAR(255) NOT NULL DEFAULT '' AFTER `code`;
|
||||
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (400, 'SITEWRITER', 'Manage and edit websites, messages, and analytics');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (401, 'SITEWRITER_CONTACT', 'Manage messages sent via website contact forms');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (402, 'SITEWRITER_ANALYTICS', 'View website analytics');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (403, 'SITEWRITER_EDIT', 'Edit website content');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (404, 'SITEWRITER_FILES', 'Manage and upload files');
|
@ -1,35 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `userkeys` (
|
||||
`uid` INT(11) NOT NULL,
|
||||
`key` VARCHAR(100) NOT NULL,
|
||||
`created` DATETIME NULL DEFAULT NULL,
|
||||
`typeid` INT(11) NOT NULL,
|
||||
PRIMARY KEY (`uid`),
|
||||
INDEX `fk_userkeys_userkeytypes1_idx` (`typeid` ASC),
|
||||
CONSTRAINT `fk_userkeys_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounthub`.`accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION,
|
||||
CONSTRAINT `fk_userkeys_userkeytypes1`
|
||||
FOREIGN KEY (`typeid`)
|
||||
REFERENCES `accounthub`.`userkeytypes` (`typeid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARACTER SET = utf8;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `userkeytypes` (
|
||||
`typeid` INT(11) NOT NULL,
|
||||
`typename` VARCHAR(45) NOT NULL,
|
||||
PRIMARY KEY (`typeid`, `typename`))
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARACTER SET = utf8;
|
||||
|
||||
INSERT INTO `userkeytypes` (`typeid`, `typename`) VALUES (1, 'RSSAtomFeed');
|
||||
INSERT INTO `userkeytypes` (`typeid`, `typename`) VALUES (2, 'Other');
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
DROP TABLE IF EXISTS `available_apps`;
|
||||
DROP TABLE IF EXISTS `apps`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `userloginkeys` (
|
||||
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||
`key` VARCHAR(255) NOT NULL,
|
||||
`expires` DATETIME NULL DEFAULT NULL,
|
||||
`uid` INT(11) NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`, `key`),
|
||||
UNIQUE INDEX `id_UNIQUE` (`id` ASC),
|
||||
UNIQUE INDEX `key_UNIQUE` (`key` ASC),
|
||||
INDEX `fk_userloginkeys_accounts1_idx` (`uid` ASC),
|
||||
CONSTRAINT `fk_userloginkeys_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARACTER SET = utf8;
|
||||
|
||||
ALTER TABLE `userloginkeys`
|
||||
ADD COLUMN `appname` VARCHAR(255) NOT NULL AFTER `uid`;
|
||||
ALTER TABLE `userloginkeys`
|
||||
ADD COLUMN `appicon` TINYTEXT NULL DEFAULT NULL AFTER `appname`;
|
||||
ALTER TABLE `apikeys`
|
||||
ADD COLUMN `type` VARCHAR(45) NOT NULL DEFAULT 'FULL' AFTER `notes`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `apppasswords` (
|
||||
`passid` INT(11) NOT NULL AUTO_INCREMENT,
|
||||
`hash` VARCHAR(255) NOT NULL,
|
||||
`uid` INT(11) NOT NULL,
|
||||
`description` VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY (`passid`, `uid`),
|
||||
UNIQUE INDEX `passid_UNIQUE` (`passid` ASC),
|
||||
INDEX `fk_apppasswords_accounts1_idx` (`uid` ASC),
|
||||
CONSTRAINT `fk_apppasswords_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARACTER SET = utf8;
|
89
feed.php
89
feed.php
@ -1,89 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
require __DIR__ . "/required.php";
|
||||
|
||||
date_default_timezone_set('UTC');
|
||||
|
||||
use \FeedWriter\RSS1;
|
||||
use \FeedWriter\RSS2;
|
||||
use \FeedWriter\ATOM;
|
||||
|
||||
if (empty($_GET['key']) || empty($_GET['type'])) {
|
||||
http_response_code(400);
|
||||
die("400 Bad Request: please send a user key and a feed type");
|
||||
}
|
||||
|
||||
if (!$database->has('userkeys', ['key' => $_GET['key']])) {
|
||||
http_response_code(403);
|
||||
die("403 Forbidden: provide valid key");
|
||||
}
|
||||
|
||||
$uid = $database->get('userkeys', 'uid', ['key' => $_GET['key']]);
|
||||
$user = new User($uid);
|
||||
switch ($user->getStatus()->get()) {
|
||||
case AccountStatus::NORMAL:
|
||||
case AccountStatus::CHANGE_PASSWORD:
|
||||
case AccountStatus::ALERT_ON_ACCESS:
|
||||
break;
|
||||
default:
|
||||
http_response_code(403);
|
||||
die("403 Forbidden: user account not active");
|
||||
}
|
||||
|
||||
$notifications = Notifications::get($user);
|
||||
|
||||
switch ($_GET['type']) {
|
||||
case "rss1":
|
||||
$feed = new RSS1();
|
||||
break;
|
||||
case "rss":
|
||||
case "rss2":
|
||||
$feed = new RSS2();
|
||||
break;
|
||||
case "atom":
|
||||
$feed = new ATOM();
|
||||
break;
|
||||
default:
|
||||
http_response_code(400);
|
||||
die("400 Bad Request: feed parameter must have a value of \"rss\", \"rss1\", \"rss2\" or \"atom\".");
|
||||
}
|
||||
|
||||
$feed->setTitle($Strings->build("Notifications from server for user", ['server' => $SETTINGS['site_title'], 'user' => $user->getName()], false));
|
||||
|
||||
if (strpos($SETTINGS['url'], "http") === 0) {
|
||||
$url = $SETTINGS['url'];
|
||||
} else {
|
||||
$url = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . (($_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) ? ":" . $_SERVER['SERVER_PORT'] : "") . $SETTINGS['url'];
|
||||
}
|
||||
|
||||
$feed->setLink($url);
|
||||
|
||||
foreach ($notifications as $n) {
|
||||
$item = $feed->createNewItem();
|
||||
$item->setTitle($n['title']);
|
||||
if (empty($n['url'])) {
|
||||
$item->setLink($url);
|
||||
} else {
|
||||
$item->setLink($n['url']);
|
||||
}
|
||||
$item->setDate(strtotime($n['timestamp']));
|
||||
if ($n['sensitive']) {
|
||||
$content = $Strings->get("Sensitive content hidden", false);
|
||||
} else {
|
||||
$content = $n['content'];
|
||||
}
|
||||
if ($_GET['type'] == "atom") {
|
||||
$item->setContent($content);
|
||||
} else {
|
||||
$item->setDescription($content);
|
||||
}
|
||||
$feed->addItem($item);
|
||||
}
|
||||
|
||||
$feed->printFeed();
|
182
home.php
182
home.php
@ -1,6 +1,180 @@
|
||||
<?php
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
require_once __DIR__ . "/required.php";
|
||||
|
||||
header("Location: app.php");
|
||||
if ($_SESSION['loggedin'] != true) {
|
||||
header('Location: index.php');
|
||||
die("Session expired. Log in again to continue.");
|
||||
} else if (is_empty($_SESSION['password'])) {
|
||||
header('Location: index.php');
|
||||
die("You need to log in again.");
|
||||
}
|
||||
|
||||
require_once __DIR__ . "/pages.php";
|
||||
|
||||
$pageid = "home";
|
||||
if (!is_empty($_GET['page'])) {
|
||||
if (array_key_exists($_GET['page'], PAGES)) {
|
||||
$pageid = $_GET['page'];
|
||||
} else {
|
||||
$pageid = "404";
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title><?php echo SITE_TITLE; ?></title>
|
||||
|
||||
<link href="static/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="static/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link href="static/css/app.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-4 col-sm-offset-3 col-md-offset-4 col-lg-offset-4">
|
||||
<img class="img-responsive banner-image" src="static/img/logo.svg" />
|
||||
</div>
|
||||
</div>
|
||||
<nav class="navbar navbar-inverse">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<?php
|
||||
if (PAGES[$pageid]['title'] == "{DEFAULT}") {
|
||||
?>
|
||||
<span class="navbar-brand">
|
||||
<?php
|
||||
lang2("welcome user", ["user" => $_SESSION['realname']]);
|
||||
?>
|
||||
</span>
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
<a class="navbar-brand" href="home.php?page=home">
|
||||
<?php
|
||||
// add breadcrumb thing
|
||||
lang("home");
|
||||
echo " <i class=\"fa fa-caret-right\"></i> ";
|
||||
lang(PAGES[$pageid]['title']);
|
||||
?>
|
||||
</a>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbar-collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-gears fa-fw"></i> <?php lang("options") ?> <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><a href="home.php?page=security"><i class="fa fa-lock fa-fw"></i> <?php lang("account security") ?></a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="action.php?action=signout"><i class="fa fa-sign-out fa-fw"></i> <?php lang("sign out") ?></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<?php
|
||||
// Alert messages
|
||||
if (!is_empty($_GET['msg']) && array_key_exists($_GET['msg'], MESSAGES)) {
|
||||
// optional string generation argument
|
||||
if (is_empty($_GET['arg'])) {
|
||||
$alertmsg = lang(MESSAGES[$_GET['msg']]['string'], false);
|
||||
} else {
|
||||
$alertmsg = lang2(MESSAGES[$_GET['msg']]['string'], ["arg" => $_GET['arg']], false);
|
||||
}
|
||||
$alerttype = MESSAGES[$_GET['msg']]['type'];
|
||||
$alerticon = "square-o";
|
||||
switch (MESSAGES[$_GET['msg']]['type']) {
|
||||
case "danger":
|
||||
$alerticon = "times";
|
||||
break;
|
||||
case "warning":
|
||||
$alerticon = "exclamation-triangle";
|
||||
break;
|
||||
case "info":
|
||||
$alerticon = "info-circle";
|
||||
break;
|
||||
case "success":
|
||||
$alerticon = "check";
|
||||
break;
|
||||
}
|
||||
echo <<<END
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-4 col-sm-offset-3 col-md-offset-4 col-lg-offset-4">
|
||||
<div class="alert alert-dismissible alert-$alerttype">
|
||||
<button type="button" class="close">×</button>
|
||||
<i class="fa fa-$alerticon"></i> $alertmsg
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
END;
|
||||
}
|
||||
?>
|
||||
<div class="row">
|
||||
<?php
|
||||
// Center the widgets horizontally on the screen
|
||||
$appcount = count(APPS[$pageid]);
|
||||
if ($appcount == 1) {
|
||||
?>
|
||||
<div class="hidden-xs col-sm-3 col-md-4 col-lg-4">
|
||||
<!-- Empty placeholder column for nice center-align -->
|
||||
</div>
|
||||
<?php
|
||||
} else if ($appcount == 2) {
|
||||
?>
|
||||
<div class="hidden-xs hidden-sm col-md-2 col-lg-2">
|
||||
<!-- Empty placeholder column for nice center-align -->
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Load app widgets
|
||||
foreach (APPS[$pageid] as $app) {
|
||||
if (file_exists(__DIR__ . "/apps/" . $app . ".php")) {
|
||||
include_once __DIR__ . "/apps/" . $app . ".php";
|
||||
$apptitle = ($APPS[$app]['i18n'] === TRUE ? lang($APPS[$app]['title'], false) : $APPS[$app]['title']);
|
||||
$appicon = (is_empty($APPS[$app]['icon']) ? "" : "fa fa-fw fa-" . $APPS[$app]['icon']);
|
||||
$apptype = (is_empty($APPS[$app]['type']) ? "default" : $APPS[$app]['type']);
|
||||
$appcontent = $APPS[$app]['content'];
|
||||
echo <<<END
|
||||
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-4">
|
||||
<div class="panel panel-$apptype">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><i class="$appicon"></i> $apptitle </h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
$appcontent
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
END;
|
||||
}
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<?php echo LICENSE_TEXT; ?><br />
|
||||
Copyright © <?php echo date('Y'); ?> <?php echo COPYRIGHT_NAME; ?>
|
||||
</div>
|
||||
</div>
|
||||
<script src="static/js/jquery-3.2.1.min.js"></script>
|
||||
<script src="static/js/bootstrap.min.js"></script>
|
||||
<script src="static/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
309
index.php
309
index.php
@ -1,113 +1,226 @@
|
||||
<?php
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . "/required.php";
|
||||
|
||||
// if we're logged in, we don't need to be here.
|
||||
if (!empty($_SESSION['loggedin']) && $_SESSION['loggedin'] === true && !isset($_GET['permissionerror'])) {
|
||||
header('Location: app.php');
|
||||
require_once __DIR__ . "/lib/login.php";
|
||||
|
||||
// If we're logged in, we don't need to be here.
|
||||
if ($_SESSION['loggedin'] && !is_empty($_SESSION['password'])) {
|
||||
header('Location: home.php');
|
||||
die();
|
||||
// This branch will likely run if the user signed in from a different app.
|
||||
} else if ($_SESSION['loggedin'] && is_empty($_SESSION['password'])) {
|
||||
$alert = lang("sign in again", false);
|
||||
$alerttype = "info";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show a simple HTML page with a line of text and a button. Matches the UI of
|
||||
* the AccountHub login flow.
|
||||
*
|
||||
* @global type $SETTINGS
|
||||
* @global type $SECURE_NONCE
|
||||
* @global type $Strings
|
||||
* @param string $title Text to show, passed through i18n
|
||||
* @param string $button Button text, passed through i18n
|
||||
* @param string $url URL for the button
|
||||
*/
|
||||
function showHTML(string $title, string $button, string $url) {
|
||||
global $SETTINGS, $SECURE_NONCE, $Strings;
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title><?php echo $SETTINGS['site_title']; ?></title>
|
||||
|
||||
<link rel="icon" href="static/img/logo.svg">
|
||||
|
||||
<link href="static/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style nonce="<?php echo $SECURE_NONCE; ?>">
|
||||
.display-5 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 300;
|
||||
line-height: 1.2;
|
||||
/* Authenticate user */
|
||||
$username_ok = false;
|
||||
$multiauth = false;
|
||||
$change_password = false;
|
||||
if ($VARS['progress'] == "1") {
|
||||
if (!RECAPTCHA_ENABLED || (RECAPTCHA_ENABLED && verifyReCaptcha($VARS['g-recaptcha-response']))) {
|
||||
$autherror = "";
|
||||
if (user_exists($VARS['username'])) {
|
||||
$status = get_account_status($VARS['username'], $error);
|
||||
switch ($status) {
|
||||
case "LOCKED_OR_DISABLED":
|
||||
$alert = lang("account locked", false);
|
||||
break;
|
||||
case "TERMINATED":
|
||||
$alert = lang("account terminated", false);
|
||||
break;
|
||||
case "CHANGE_PASSWORD":
|
||||
$alert = lang("password expired", false);
|
||||
$alerttype = "info";
|
||||
$_SESSION['username'] = strtolower($VARS['username']);
|
||||
$change_password = true;
|
||||
break;
|
||||
case "NORMAL":
|
||||
$username_ok = true;
|
||||
break;
|
||||
case "ALERT_ON_ACCESS":
|
||||
sendLoginAlertEmail($VARS['username']);
|
||||
$username_ok = true;
|
||||
break;
|
||||
default:
|
||||
if (!is_empty($error)) {
|
||||
$alert = $error;
|
||||
break;
|
||||
}
|
||||
$alert = lang("login error", false);
|
||||
break;
|
||||
}
|
||||
if ($username_ok) {
|
||||
if (authenticate_user($VARS['username'], $VARS['password'], $autherror)) {
|
||||
$_SESSION['passok'] = true; // stop logins using only username and authcode
|
||||
if (userHasTOTP($VARS['username'])) {
|
||||
$multiauth = true;
|
||||
$_SESSION['password'] = $VARS['password'];
|
||||
} else {
|
||||
doLoginUser($VARS['username'], $VARS['password']);
|
||||
insertAuthLog(1, $_SESSION['uid']);
|
||||
header('Location: home.php');
|
||||
die("Logged in, go to home.php");
|
||||
}
|
||||
} else {
|
||||
if (!is_empty($autherror)) {
|
||||
$alert = $autherror;
|
||||
insertAuthLog(2, null, "Username: " . $VARS['username']);
|
||||
} else {
|
||||
$alert = lang("login incorrect", false);
|
||||
insertAuthLog(2, null, "Username: " . $VARS['username']);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // User does not exist anywhere
|
||||
$alert = lang("login incorrect", false);
|
||||
insertAuthLog(2, null, "Username: " . $VARS['username']);
|
||||
}
|
||||
|
||||
.banner-image {
|
||||
max-height: 100px;
|
||||
margin: 2em auto;
|
||||
border: 1px solid grey;
|
||||
border-radius: 15%;
|
||||
} else {
|
||||
$alert = lang("captcha error", false);
|
||||
insertAuthLog(8, null, "Username: " . $VARS['username']);
|
||||
}
|
||||
} else if ($VARS['progress'] == "2") {
|
||||
if ($_SESSION['passok'] !== true) {
|
||||
// stop logins using only username and authcode
|
||||
sendError("Password integrity check failed!");
|
||||
}
|
||||
if (verifyTOTP($VARS['username'], $VARS['authcode'])) {
|
||||
doLoginUser($VARS['username'], $VARS['password']);
|
||||
insertAuthLog(1, $_SESSION['uid']);
|
||||
header('Location: home.php');
|
||||
die("Logged in, go to home.php");
|
||||
} else {
|
||||
$alert = lang("2fa incorrect", false);
|
||||
insertAuthLog(6, null, "Username: " . $VARS['username']);
|
||||
}
|
||||
} else if ($VARS['progress'] == "chpasswd") {
|
||||
if (!is_empty($_SESSION['username'])) {
|
||||
$error = [];
|
||||
$result = change_password($VARS['oldpass'], $VARS['newpass'], $VARS['conpass'], $error);
|
||||
if ($result === TRUE) {
|
||||
$alert = lang(MESSAGES["password_updated"]["string"], false);
|
||||
$alerttype = MESSAGES["password_updated"]["type"];
|
||||
}
|
||||
</style>
|
||||
switch (count($error)) {
|
||||
case 1:
|
||||
$alert = lang(MESSAGES[$error[0]]["string"], false);
|
||||
$alerttype = MESSAGES[$error[0]]["type"];
|
||||
break;
|
||||
case 2:
|
||||
$alert = lang2(MESSAGES[$error[0]]["string"], ["arg" => $error[1]], false);
|
||||
$alerttype = MESSAGES[$error[0]]["type"];
|
||||
break;
|
||||
default:
|
||||
$alert = lang(MESSAGES["generic_op_error"]["string"], false);
|
||||
$alerttype = MESSAGES["generic_op_error"]["type"];
|
||||
}
|
||||
} else {
|
||||
session_destroy();
|
||||
header('Location: index.php');
|
||||
die();
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 text-center">
|
||||
<img class="banner-image" src="./static/img/logo.svg" />
|
||||
</div>
|
||||
<title><?php echo SITE_TITLE; ?></title>
|
||||
|
||||
<div class="col-12 text-center">
|
||||
<h1 class="display-5 mb-4"><?php $Strings->get($title); ?></h1>
|
||||
</div>
|
||||
<link href="static/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="static/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link href="static/css/app.css" rel="stylesheet">
|
||||
<?php if (RECAPTCHA_ENABLED) { ?>
|
||||
<script src='https://www.google.com/recaptcha/api.js'></script>
|
||||
<?php } ?>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-4 col-sm-offset-3 col-md-offset-4 col-lg-offset-4">
|
||||
<div>
|
||||
<img class="img-responsive banner-image" src="static/img/logo.svg" />
|
||||
</div>
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><?php lang("sign in"); ?></h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form action="" method="POST">
|
||||
<?php
|
||||
if (!is_empty($alert)) {
|
||||
$alerttype = isset($alerttype) ? $alerttype : "danger";
|
||||
?>
|
||||
<div class="alert alert-<?php echo $alerttype ?>">
|
||||
<?php
|
||||
switch ($alerttype) {
|
||||
case "danger":
|
||||
$alerticon = "times";
|
||||
break;
|
||||
case "warning":
|
||||
$alerticon = "exclamation-triangle";
|
||||
break;
|
||||
case "info":
|
||||
$alerticon = "info-circle";
|
||||
break;
|
||||
case "success":
|
||||
$alerticon = "check";
|
||||
break;
|
||||
default:
|
||||
$alerticon = "square-o";
|
||||
}
|
||||
?>
|
||||
<i class="fa fa-fw fa-<?php echo $alerticon ?>"></i> <?php echo $alert ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
<div class="col-12 col-sm-8 col-lg-6">
|
||||
<div class="card mt-4">
|
||||
<div class="card-body">
|
||||
<a href="<?php echo $url; ?>" class="btn btn-primary btn-block"><?php $Strings->get($button); ?></a>
|
||||
if (!$multiauth && !$change_password) {
|
||||
?>
|
||||
<input type="text" class="form-control" name="username" placeholder="<?php lang("username"); ?>" required="required" autofocus /><br />
|
||||
<input type="password" class="form-control" name="password" placeholder="<?php lang("password"); ?>" required="required" /><br />
|
||||
<?php if (RECAPTCHA_ENABLED) { ?>
|
||||
<div class="g-recaptcha" data-sitekey="<?php echo RECAPTCHA_SITE_KEY; ?>"></div>
|
||||
<br />
|
||||
<?php } ?>
|
||||
<input type="hidden" name="progress" value="1" />
|
||||
<?php
|
||||
} else if ($multiauth) {
|
||||
?>
|
||||
<div class="alert alert-info">
|
||||
<?php lang("2fa prompt"); ?>
|
||||
</div>
|
||||
<input type="text" class="form-control" name="authcode" placeholder="<?php lang("authcode"); ?>" required="required" autocomplete="off" autofocus /><br />
|
||||
<input type="hidden" name="progress" value="2" />
|
||||
<input type="hidden" name="username" value="<?php echo $VARS['username']; ?>" />
|
||||
<?php
|
||||
} else if ($change_password) {
|
||||
?>
|
||||
<input type="password" class="form-control" name="oldpass" placeholder="Current password" required="required" autocomplete="new-password" autofocus /><br />
|
||||
<input type="password" class="form-control" name="newpass" placeholder="New password" required="required" autocomplete="off" /><br />
|
||||
<input type="password" class="form-control" name="conpass" placeholder="New password (again)" required="required" autocomplete="off" /><br />
|
||||
<input type="hidden" name="progress" value="chpasswd" />
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<?php lang("continue"); ?>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<?php echo LICENSE_TEXT; ?><br />
|
||||
Copyright © <?php echo date('Y'); ?> <?php echo COPYRIGHT_NAME; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
if (!empty($_GET['logout'])) {
|
||||
showHTML("You have been logged out.", "Log in again", "./index.php");
|
||||
die();
|
||||
}
|
||||
if (empty($_SESSION["login_code"])) {
|
||||
$redirecttologin = true;
|
||||
} else {
|
||||
try {
|
||||
$uid = LoginKey::getuid($_SESSION["login_code"]);
|
||||
|
||||
$user = new User($uid);
|
||||
Session::start($user);
|
||||
$_SESSION["login_code"] = null;
|
||||
header('Location: app.php');
|
||||
showHTML("Logged in", "Continue", "./app.php");
|
||||
die();
|
||||
} catch (Exception $ex) {
|
||||
$redirecttologin = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($redirecttologin) {
|
||||
try {
|
||||
$code = LoginKey::generate($SETTINGS["site_title"], "../static/img/logo.svg");
|
||||
|
||||
$_SESSION["login_code"] = $code;
|
||||
|
||||
$loginurl = "./login/?code=" . htmlentities($code) . "&redirect=" . htmlentities($_SERVER["REQUEST_URI"]);
|
||||
|
||||
header("Location: $loginurl");
|
||||
showHTML("Continue", "Continue", $loginurl);
|
||||
die();
|
||||
} catch (Exception $ex) {
|
||||
sendError($ex->getMessage());
|
||||
}
|
||||
}
|
||||
<script src="static/js/jquery-3.2.1.min.js"></script>
|
||||
<script src="static/js/bootstrap.min.js"></script>
|
||||
</body>
|
||||
</html>
|
53
lang/en_us.php
Normal file
53
lang/en_us.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
$STRINGS = [
|
||||
"sign in" => "Sign In",
|
||||
"username" => "Username",
|
||||
"password" => "Password",
|
||||
"continue" => "Continue",
|
||||
"authcode" => "Authentication code",
|
||||
"2fa prompt" => "Enter the six-digit code from your mobile authenticator app.",
|
||||
"2fa incorrect" => "Authentication code incorrect.",
|
||||
"login incorrect" => "Login incorrect.",
|
||||
"login successful" => "Login successful.",
|
||||
"login error" => "There was a server problem. Try again later.",
|
||||
"account locked" => "This account has been disabled. Contact technical support.",
|
||||
"password expired" => "You must change your password before continuing.",
|
||||
"account terminated" => "Account terminated. Access denied.",
|
||||
"account state error" => "Your account state is not stable. Log out, restart your browser, and try again.",
|
||||
"password on 500 list" => "The given password is ranked number {arg} out of the 500 most common passwords. Try a different one.",
|
||||
"welcome user" => "Welcome, {user}!",
|
||||
"change password" => "Change password",
|
||||
"security options" => "Security options",
|
||||
"account security" => "Account security",
|
||||
"sign out" => "Sign out",
|
||||
"settings" => "Settings",
|
||||
"options" => "Options",
|
||||
"404 error" => "404 Error",
|
||||
"page not found" => "Page not found.",
|
||||
"current password incorrect" => "The current password is incorrect. Try again.",
|
||||
"new password mismatch" => "The new passwords did not match. Try again.",
|
||||
"weak password" => "Password does not meet requirements.",
|
||||
"password updated" => "Password updated successfully.",
|
||||
"setup 2fa" => "Setup 2-factor authentication",
|
||||
"2fa removed" => "2-factor authentication disabled.",
|
||||
"2fa enabled" => "2-factor authentication activated.",
|
||||
"remove 2fa" => "Disable 2-factor authentication",
|
||||
"2fa explained" => "2-factor authentication adds more security to your account. You'll need an app such as Google Authenticator on your smartphone. When you have the app installed, you can enable 2-factor authentication by clicking the button below and scanning a QR code with the app. Whenever you sign in in the future, you'll need to input a six-digit code from your phone into the login page when prompted. You can disable 2-factor authentication from this page if you change your mind.",
|
||||
"2fa active" => "2-factor authentication is active on your account. To remove 2fa, reset your authentication secret, or change to a new security device, click the button below.",
|
||||
"enable 2fa" => "Enable 2-factor authentication",
|
||||
"scan 2fa qrcode" => "Scan the QR Code with the authenticator app, or enter the secret key manually.",
|
||||
"confirm 2fa" => "Finish setup",
|
||||
"invalid parameters" => "Invalid request parameters.",
|
||||
"ldap server error" => "The LDAP server returned an error: {arg}",
|
||||
"user does not exist" => "User does not exist.",
|
||||
"captcha error" => "There was a problem with the CAPTCHA (robot test). Try again.",
|
||||
"home" => "Home",
|
||||
"ldap error" => "LDAP error: {error}",
|
||||
"old and new passwords match" => "Your current and new passwords are the same.",
|
||||
"generic op error" => "An unknown error occurred. Try again later.",
|
||||
"password complexity insufficent" => "The new password does not meet the minumum requirements defined by your system administrator.",
|
||||
"error loading widget" => "There was a problem loading this app.",
|
||||
"open app" => "Open App",
|
||||
"sign in again" => "Please sign in again to continue."
|
||||
];
|
@ -1,9 +1,5 @@
|
||||
<?php
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
define("MESSAGES", [
|
||||
"old_password_mismatch" => [
|
||||
"string" => "current password incorrect",
|
||||
@ -29,10 +25,6 @@ define("MESSAGES", [
|
||||
"string" => "2fa enabled",
|
||||
"type" => "success"
|
||||
],
|
||||
"2fa_wrong_code" => [
|
||||
"string" => "2fa incorrect",
|
||||
"type" => "danger"
|
||||
],
|
||||
"invalid_parameters" => [
|
||||
"string" => "invalid parameters",
|
||||
"type" => "danger"
|
||||
@ -45,6 +37,10 @@ define("MESSAGES", [
|
||||
"string" => "account state error",
|
||||
"type" => "danger"
|
||||
],
|
||||
"ldap_error" => [
|
||||
"string" => "ldap server error",
|
||||
"type" => "danger"
|
||||
],
|
||||
"passwords_same" => [
|
||||
"string" => "old and new passwords match",
|
||||
"type" => "danger"
|
||||
@ -56,25 +52,5 @@ define("MESSAGES", [
|
||||
"generic_op_error" => [
|
||||
"string" => "generic op error",
|
||||
"type" => "danger"
|
||||
],
|
||||
"pin_updated" => [
|
||||
"string" => "pin updated",
|
||||
"type" => "success"
|
||||
],
|
||||
"new_pin_mismatch" => [
|
||||
"string" => "new pin mismatch",
|
||||
"type" => "danger"
|
||||
],
|
||||
"invalid_pin_format" => [
|
||||
"string" => "invalid pin format",
|
||||
"type" => "danger"
|
||||
],
|
||||
"notification_deleted" => [
|
||||
"string" => "Notification deleted.",
|
||||
"type" => "success"
|
||||
],
|
||||
"feed_key_reset" => [
|
||||
"string" => "Feed key reset.",
|
||||
"type" => "success"
|
||||
]
|
||||
]);
|
@ -1,17 +0,0 @@
|
||||
{
|
||||
"setup 2fa": "Setup 2-factor authentication",
|
||||
"2fa removed": "2-factor authentication disabled.",
|
||||
"2fa enabled": "2-factor authentication activated.",
|
||||
"remove 2fa": "Disable 2-factor authentication",
|
||||
"2fa explained": "2-factor authentication adds more security to your account. You can use the Auth Keys (key icon) feature of the Netsyms mobile app, or another TOTP-enabled app (Authy, FreeOTP, etc) on your smartphone. When you have the app installed, you can enable 2-factor authentication by clicking the button below and scanning a QR code with the app. Whenever you sign in in the future, you'll need to input a six-digit code from your phone into the login page when prompted. You can disable 2-factor authentication from this page if you change your mind.",
|
||||
"2fa active": "2-factor authentication is active on your account. To remove 2fa, reset your authentication secret, or change to a new security device, click the button below.",
|
||||
"enable 2fa": "Enable 2-factor authentication",
|
||||
"scan 2fa qrcode": "Scan the QR Code with the authenticator app, or enter the information manually. Then type in the six-digit code the app gives you and press Finish Setup.",
|
||||
"confirm 2fa": "Finish setup",
|
||||
"enter otp code": "Enter 6-digit code",
|
||||
"secret key": "Secret key",
|
||||
"label": "Label",
|
||||
"issuer": "Issuer",
|
||||
"no such code or code expired": "That code is incorrect or expired.",
|
||||
"2-factor is enabled, you need to use the QR code or manual setup for security reasons": "2-factor is enabled, you need to use the QR code or manual setup for security reasons."
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"user does not exist": "User does not exist.",
|
||||
"group does not exist": "Group does not exist.",
|
||||
"login successful": "Login successful."
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
{
|
||||
"App Passwords": "App Passwords",
|
||||
"app passwords explained": "Use app passwords instead of your actual password when logging into apps with your {site_name} login. App passwords are required in some places when you have 2-factor authentication enabled.",
|
||||
"app password setup instructions": "Use the username and password below to log in to {app_name}. You'll only be shown this password one time.",
|
||||
"App name": "App name",
|
||||
"Generate password": "Generate password",
|
||||
"Revoke password": "Revoke password",
|
||||
"You don't have any app passwords.": "You don't have any app passwords.",
|
||||
"Done": "Done",
|
||||
"App passwords are not allowed here.": "App passwords are not allowed here."
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"Create virtual notes and lists": "Create virtual notes and lists",
|
||||
"Punch in and check work schedule": "Punch in and check work schedule",
|
||||
"Manage physical items": "Manage physical items",
|
||||
"Create and publish e-newsletters": "Create and publish e-newsletters",
|
||||
"Manage users, permissions, and security": "Manage users, permissions, and security",
|
||||
"Checkout customers and manage online orders": "Checkout customers and manage online orders",
|
||||
"Build websites and manage contact form messages": "Build websites and manage contact form messages",
|
||||
"Track jobs and assigned tasks": "Track jobs and assigned tasks",
|
||||
"Change password, setup 2-factor, and add app passwords": "Change password, setup 2-factor, and add app passwords",
|
||||
"Change password, setup 2-factor, add app passwords, and change PIN": "Change password, setup 2-factor, add app passwords, and change PIN",
|
||||
"Connect mobile devices to {name} and get notifications": "Connect mobile devices to {name} and get notifications"
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"sign out": "Sign out",
|
||||
"404 error": "404 Error",
|
||||
"page not found": "Page not found.",
|
||||
"invalid parameters": "Invalid request parameters.",
|
||||
"login server error": "The login server returned an error: {arg}"
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"You have been logged out.": "You have been logged out.",
|
||||
"Log in again": "Log in again",
|
||||
"login server unavailable": "Login server unavailable. Try again later or contact technical support.",
|
||||
"no access permission": "You do not have permission to access this system.",
|
||||
"Logged in": "Logged in"
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
{
|
||||
"ldap server error": "The LDAP server returned an error: {arg}",
|
||||
"ldap error": "LDAP error: {error}"
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
{
|
||||
"Login to {app}": "Login to {app}",
|
||||
"Username not found.": "Username not found.",
|
||||
"Password for {user}": "Password for {user}",
|
||||
"Password incorrect.": "Password incorrect.",
|
||||
"Two-factor code": "Two-factor code",
|
||||
"Code incorrect.": "Code incorrect.",
|
||||
"Current password for {user}": "Current password for {user}",
|
||||
"New password": "New password",
|
||||
"New password (again)": "New password (again)",
|
||||
"Fill in all three boxes.": "Fill in all three boxes.",
|
||||
"sign in": "Sign In",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"continue": "Continue",
|
||||
"authcode": "Authentication code",
|
||||
"2fa prompt": "Enter the six-digit code from your mobile authenticator app.",
|
||||
"2fa incorrect": "Authentication code incorrect.",
|
||||
"login incorrect": "Login incorrect.",
|
||||
"account locked": "This account has been disabled. Contact technical support.",
|
||||
"password expired": "You must change your password before continuing.",
|
||||
"account terminated": "Account terminated. Access denied.",
|
||||
"account state error": "Your account state is not stable. Log out, restart your browser, and try again.",
|
||||
"Back": "Back"
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"Notifications": "Notifications",
|
||||
"Notification deleted.": "Notification deleted.",
|
||||
"Mark as read": "Mark as read",
|
||||
"Delete": "Delete",
|
||||
"All caught up!": "All caught up!",
|
||||
"Notifications from server for user": "Notifications from {server} for {user}",
|
||||
"Sensitive content hidden": "Sensitive content hidden"
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"password on 500 list": "The given password is ranked number {arg} out of the 500 most common passwords. Try a different one.",
|
||||
"change password": "Change password",
|
||||
"current password incorrect": "The current password is incorrect. Try again.",
|
||||
"new password mismatch": "The new passwords did not match. Try again.",
|
||||
"weak password": "Password does not meet requirements.",
|
||||
"password updated": "Password updated successfully.",
|
||||
"current password": "Current password",
|
||||
"new password": "New password",
|
||||
"confirm password": "New password (again)",
|
||||
"password complexity insufficent": "The new password does not meet the minumum requirements defined by your system administrator.",
|
||||
"old and new passwords match": "Your current and new passwords are the same."
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"pin explanation": "Change or set a login PIN for the Station kiosk Quick Access. PIN codes must be between one and eight digits.",
|
||||
"change pin": "Change PIN",
|
||||
"new pin": "New PIN",
|
||||
"confirm pin": "New PIN (again)",
|
||||
"pin updated": "PIN updated.",
|
||||
"new pin mismatch": "The new PINs don't match each other.",
|
||||
"invalid pin format": "PIN codes must be numeric and between one and eight digits in length."
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"sign in again": "Please sign in again to continue.",
|
||||
"login failed try on web": "There is a problem with your account. Visit AccountHub via a web browser for more information.",
|
||||
"mobile login disabled": "Mobile login has been disabled by your system administrator. Contact technical support for more information.",
|
||||
"admin alert email subject": "Alert: User login notification",
|
||||
"admin alert email message": "You (or another administrator) requested to be notified when user \"{username}\" logged in, an event which happened just now.\r\n\r\nUsername: \t{username}\r\nApplication: \t{appname}\r\nDate\/Time: \t{datetime}\r\nIP address: \t{ipaddr}\r\n\r\nThese notifications can be disabled by editing the user in ManagePanel."
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
{
|
||||
"Create Account": "Create Account",
|
||||
"Account Created": "Account Created",
|
||||
"Choose a username.": "Choose a username.",
|
||||
"Choose a password.": "Choose a password.",
|
||||
"Enter your name.": "Enter your name.",
|
||||
"Username already taken, pick another.": "Username already taken, pick another.",
|
||||
"Your password must be at least {n} characters long.": "Your password must be at least {n} characters long.",
|
||||
"That email address doesn't look right.": "That email address doesn't look right.",
|
||||
"Please enter your username (4-100 characters, alphanumeric).": "Please enter your username (4-100 characters, alphanumeric).",
|
||||
"That password is one of the most popular and insecure ever, make a better one.": "That password is one of the most popular and insecure ever, make a better one.",
|
||||
"Account creation not allowed. Contact the site administrator for an account.": "Account creation not allowed. Contact the site administrator for an account.",
|
||||
"CAPTCHA answer incorrect.": "CAPTCHA answer incorrect.",
|
||||
"That email address is already in use.": "That email address is already in use."
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
{
|
||||
"sync mobile": "Sync Mobile App",
|
||||
"scan sync qrcode": "Scan this code with the mobile app or enter the code manually.",
|
||||
"sync explained": "Access your account and apps on the go. Use a sync code to securely connect your phone or tablet to {site_name} with the Netsyms mobile app.",
|
||||
"generate sync": "Create new sync code",
|
||||
"active sync codes": "Active codes",
|
||||
"no active codes": "No active codes.",
|
||||
"done adding sync code": "Done adding code",
|
||||
"manual setup": "Manual Setup:",
|
||||
"sync key": "Sync key:",
|
||||
"url": "URL:",
|
||||
"sync code name": "Device nickname",
|
||||
"notification feed explained": "You can receive notifications via a RSS or ATOM news reader by clicking one of the buttons or manually adding a URL. Click the Reset button if you think someone else might know your feed URL (you'll need to delete and re-add the feed on all your devices).",
|
||||
"Reset": "Reset",
|
||||
"Feed key reset.": "Feed key reset.",
|
||||
"Revoke key": "Revoke key"
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"account security": "Account security",
|
||||
"security options": "Security options",
|
||||
"account options": "Account options",
|
||||
"sync": "Sync settings",
|
||||
"settings": "Settings",
|
||||
"account": "Account",
|
||||
"Home": "Home"
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
class IncorrectPasswordException extends Exception {
|
||||
public function __construct(string $message = "Incorrect password.", int $code = 0, \Throwable $previous = null) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
|
||||
class WeakPasswordException extends Exception {
|
||||
public function __construct(string $message = "Password is weak or compromised.", int $code = 0, \Throwable $previous = null) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
|
||||
class PasswordMatchException extends Exception {
|
||||
|
||||
public function __construct(string $message = "Old and new passwords are identical", int $code = 0, \Throwable $previous = null) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class PasswordMismatchException extends Exception {
|
||||
|
||||
public function __construct(string $message = "Passwords do not match", int $code = 0, \Throwable $previous = null) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
}
|
@ -1,326 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
class FormBuilder {
|
||||
|
||||
private $items = [];
|
||||
private $hiddenitems = [];
|
||||
private $title = "";
|
||||
private $icon = "";
|
||||
private $buttons = [];
|
||||
private $action = "action.php";
|
||||
private $method = "POST";
|
||||
private $id = "editform";
|
||||
|
||||
/**
|
||||
* Create a form with autogenerated HTML.
|
||||
*
|
||||
* @param string $title Form title/heading
|
||||
* @param string $icon FontAwesone icon next to the title.
|
||||
* @param string $action URL to submit the form to.
|
||||
* @param string $method Form submission method (POST, GET, etc.)
|
||||
*/
|
||||
public function __construct(string $title = "Untitled Form", string $icon = "fas fa-file-alt", string $action = "action.php", string $method = "POST") {
|
||||
$this->title = $title;
|
||||
$this->icon = $icon;
|
||||
$this->action = $action;
|
||||
$this->method = $method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the title of the form.
|
||||
* @param string $title
|
||||
*/
|
||||
public function setTitle(string $title) {
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the icon for the form.
|
||||
* @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
|
||||
*/
|
||||
public function setIcon(string $icon) {
|
||||
$this->icon = $icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the URL the form will submit to.
|
||||
* @param string $action
|
||||
*/
|
||||
public function setAction(string $action) {
|
||||
$this->action = $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the form submission method (GET, POST, etc)
|
||||
* @param string $method
|
||||
*/
|
||||
public function setMethod(string $method = "POST") {
|
||||
$this->method = $method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the form ID.
|
||||
* @param string $id
|
||||
*/
|
||||
public function setID(string $id = "editform") {
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an input to the form.
|
||||
*
|
||||
* @param string $name Element name
|
||||
* @param string $value Element value
|
||||
* @param string $type Input type (text, number, date, select, tel...)
|
||||
* @param bool $required If the element is required for form submission.
|
||||
* @param string $id Element ID
|
||||
* @param array $options Array of [value => text] pairs for a select element
|
||||
* @param string $label Text label to display near the input
|
||||
* @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
|
||||
* @param int $width Bootstrap column width for the input, out of 12.
|
||||
* @param int $minlength Minimum number of characters for the input.
|
||||
* @param int $maxlength Maximum number of characters for the input.
|
||||
* @param string $pattern Regex pattern for custom client-side validation.
|
||||
* @param string $error Message to show if the input doesn't validate.
|
||||
*/
|
||||
public function addInput(string $name, string $value = "", string $type = "text", bool $required = true, string $id = null, array $options = null, string $label = "", string $icon = "", int $width = 4, int $minlength = 1, int $maxlength = 100, string $pattern = "", string $error = "") {
|
||||
$item = [
|
||||
"name" => $name,
|
||||
"value" => $value,
|
||||
"type" => $type,
|
||||
"required" => $required,
|
||||
"label" => $label,
|
||||
"icon" => $icon,
|
||||
"width" => $width,
|
||||
"minlength" => $minlength,
|
||||
"maxlength" => $maxlength
|
||||
];
|
||||
if (!empty($id)) {
|
||||
$item["id"] = $id;
|
||||
}
|
||||
if (!empty($options) && $type == "select") {
|
||||
$item["options"] = $options;
|
||||
}
|
||||
if (!empty($pattern)) {
|
||||
$item["pattern"] = $pattern;
|
||||
}
|
||||
if (!empty($error)) {
|
||||
$item["error"] = $error;
|
||||
}
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a text input.
|
||||
*
|
||||
* @param string $name Element name
|
||||
* @param string $value Element value
|
||||
* @param bool $required If the element is required for form submission.
|
||||
* @param string $id Element ID
|
||||
* @param string $label Text label to display near the input
|
||||
* @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
|
||||
* @param int $width Bootstrap column width for the input, out of 12.
|
||||
* @param int $minlength Minimum number of characters for the input.
|
||||
* @param int $maxlength Maximum number of characters for the input.
|
||||
* @param string $pattern Regex pattern for custom client-side validation.
|
||||
* @param string $error Message to show if the input doesn't validate.
|
||||
*/
|
||||
public function addTextInput(string $name, string $value = "", bool $required = true, string $id = "", string $label = "", string $icon = "", int $width = 4, int $minlength = 1, int $maxlength = 100, string $pattern = "", string $error = "") {
|
||||
$this->addInput($name, $value, "text", $required, $id, null, $label, $icon, $width, $minlength, $maxlength, $pattern, $error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a select dropdown.
|
||||
*
|
||||
* @param string $name Element name
|
||||
* @param string $value Element value
|
||||
* @param bool $required If the element is required for form submission.
|
||||
* @param string $id Element ID
|
||||
* @param array $options Array of [value => text] pairs for a select element
|
||||
* @param string $label Text label to display near the input
|
||||
* @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
|
||||
* @param int $width Bootstrap column width for the input, out of 12.
|
||||
*/
|
||||
public function addSelect(string $name, string $value = "", bool $required = true, string $id = null, array $options = null, string $label = "", string $icon = "", int $width = 4) {
|
||||
$this->addInput($name, $value, "select", $required, $id, $options, $label, $icon, $width);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a button to the form.
|
||||
*
|
||||
* @param string $text Text string to show on the button.
|
||||
* @param string $icon FontAwesome icon to show next to the text.
|
||||
* @param string $href If not null, the button will actually be a hyperlink.
|
||||
* @param string $type Usually "button" or "submit". Ignored if $href is set.
|
||||
* @param string $id The element ID.
|
||||
* @param string $name The element name for the button.
|
||||
* @param string $value The form value for the button. Ignored if $name is null.
|
||||
* @param string $class The CSS classes for the button, if a standard success-colored one isn't right.
|
||||
*/
|
||||
public function addButton(string $text, string $icon = "", string $href = null, string $type = "button", string $id = null, string $name = null, string $value = "", string $class = "btn btn-success") {
|
||||
$button = [
|
||||
"text" => $text,
|
||||
"icon" => $icon,
|
||||
"class" => $class,
|
||||
"type" => $type,
|
||||
"id" => $id,
|
||||
"href" => $href,
|
||||
"name" => $name,
|
||||
"value" => $value
|
||||
];
|
||||
$this->buttons[] = $button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a hidden input.
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
*/
|
||||
public function addHiddenInput(string $name, string $value) {
|
||||
$this->hiddenitems[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the form HTML.
|
||||
* @param bool $echo If false, returns HTML string instead of outputting it.
|
||||
*/
|
||||
public function generate(bool $echo = true) {
|
||||
$html = <<<HTMLTOP
|
||||
<form action="$this->action" method="$this->method" id="$this->id">
|
||||
<div class="card">
|
||||
<h3 class="card-header d-flex">
|
||||
<div>
|
||||
<i class="$this->icon"></i> $this->title
|
||||
</div>
|
||||
</h3>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
HTMLTOP;
|
||||
|
||||
foreach ($this->items as $item) {
|
||||
$required = $item["required"] ? "required" : "";
|
||||
$id = empty($item["id"]) ? "" : "id=\"$item[id]\"";
|
||||
$pattern = empty($item["pattern"]) ? "" : "pattern=\"$item[pattern]\"";
|
||||
if (empty($item['type'])) {
|
||||
$item['type'] = "text";
|
||||
}
|
||||
$itemhtml = "";
|
||||
$itemlabel = "";
|
||||
|
||||
if ($item['type'] == "textarea") {
|
||||
$itemlabel = "<label class=\"mb-0\"><i class=\"$item[icon]\"></i> $item[label]:</label>";
|
||||
} else if ($item['type'] != "checkbox") {
|
||||
$itemlabel = "<label class=\"mb-0\">$item[label]:</label>";
|
||||
}
|
||||
$strippedlabel = strip_tags($item['label']);
|
||||
$itemhtml .= <<<ITEMTOP
|
||||
\n\n <div class="col-12 col-md-$item[width]">
|
||||
<div class="form-group mb-3">
|
||||
$itemlabel
|
||||
ITEMTOP;
|
||||
$inputgrouptop = <<<INPUTG
|
||||
\n <div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="$item[icon]"></i></span>
|
||||
</div>
|
||||
INPUTG;
|
||||
switch ($item['type']) {
|
||||
case "select":
|
||||
$itemhtml .= $inputgrouptop;
|
||||
$itemhtml .= <<<SELECT
|
||||
\n <select class="form-control" name="$item[name]" aria-label="$strippedlabel" $required>
|
||||
SELECT;
|
||||
foreach ($item['options'] as $value => $label) {
|
||||
$selected = "";
|
||||
if (!empty($item['value']) && $value == $item['value']) {
|
||||
$selected = " selected";
|
||||
}
|
||||
$itemhtml .= "\n <option value=\"$value\"$selected>$label</option>";
|
||||
}
|
||||
$itemhtml .= "\n </select>";
|
||||
break;
|
||||
case "checkbox":
|
||||
$itemhtml .= $inputgrouptop;
|
||||
$itemhtml .= <<<CHECKBOX
|
||||
\n <div class="form-group form-check">
|
||||
<input type="checkbox" name="$item[name]" $id class="form-check-input" value="$item[value]" $required aria-label="$strippedlabel">
|
||||
<label class="form-check-label">$item[label]</label>
|
||||
</div>
|
||||
CHECKBOX;
|
||||
break;
|
||||
case "textarea":
|
||||
$val = htmlentities($item['value']);
|
||||
$itemhtml .= <<<TEXTAREA
|
||||
\n <textarea class="form-control" id="info" name="$item[name]" aria-label="$strippedlabel" minlength="$item[minlength]" maxlength="$item[maxlength]" $required>$val</textarea>
|
||||
TEXTAREA;
|
||||
break;
|
||||
default:
|
||||
$itemhtml .= $inputgrouptop;
|
||||
$itemhtml .= <<<INPUT
|
||||
\n <input type="$item[type]" name="$item[name]" $id class="form-control" aria-label="$strippedlabel" minlength="$item[minlength]" maxlength="$item[maxlength]" $pattern value="$item[value]" $required />
|
||||
INPUT;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!empty($item["error"])) {
|
||||
$itemhtml .= <<<ERROR
|
||||
\n <div class="invalid-feedback">
|
||||
$item[error]
|
||||
</div>
|
||||
ERROR;
|
||||
}
|
||||
if ($item["type"] != "textarea") {
|
||||
$itemhtml .= "\n </div>";
|
||||
}
|
||||
$itemhtml .= <<<ITEMBOTTOM
|
||||
\n </div>
|
||||
</div>\n
|
||||
ITEMBOTTOM;
|
||||
$html .= $itemhtml;
|
||||
}
|
||||
|
||||
$html .= <<<HTMLBOTTOM
|
||||
|
||||
</div>
|
||||
</div>
|
||||
HTMLBOTTOM;
|
||||
|
||||
if (!empty($this->buttons)) {
|
||||
$html .= "\n <div class=\"card-footer d-flex\">";
|
||||
foreach ($this->buttons as $btn) {
|
||||
$btnhtml = "";
|
||||
$inner = "<i class=\"$btn[icon]\"></i> $btn[text]";
|
||||
$id = empty($btn['id']) ? "" : "id=\"$btn[id]\"";
|
||||
if (!empty($btn['href'])) {
|
||||
$btnhtml = "<a href=\"$btn[href]\" class=\"$btn[class]\" $id>$inner</a>";
|
||||
} else {
|
||||
$name = empty($btn['name']) ? "" : "name=\"$btn[name]\"";
|
||||
$value = (!empty($btn['name']) && !empty($btn['value'])) ? "value=\"$btn[value]\"" : "";
|
||||
$btnhtml = "<button type=\"$btn[type]\" class=\"$btn[class]\" $id $name $value>$inner</button>";
|
||||
}
|
||||
$html .= "\n $btnhtml";
|
||||
}
|
||||
$html .= "\n </div>";
|
||||
}
|
||||
|
||||
$html .= "\n </div>";
|
||||
foreach ($this->hiddenitems as $name => $value) {
|
||||
$value = htmlentities($value);
|
||||
$html .= "\n <input type=\"hidden\" name=\"$name\" value=\"$value\" />";
|
||||
}
|
||||
$html .= "\n</form>\n";
|
||||
|
||||
if ($echo) {
|
||||
echo $html;
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
<?php
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
class IPUtils {
|
||||
|
||||
/**
|
||||
* Check if a given ipv4 address is in a given cidr
|
||||
* @param string $ip IP to check in IPV4 format eg. 127.0.0.1
|
||||
* @param string $range IP/CIDR netmask eg. 127.0.0.0/24, also 127.0.0.1 is accepted and /32 assumed
|
||||
* @return boolean true if the ip is in this range / false if not.
|
||||
* @author Thorsten Ott <https://gist.github.com/tott/7684443>
|
||||
*/
|
||||
public static function ip4_in_cidr($ip, $cidr) {
|
||||
if (strpos($cidr, '/') == false) {
|
||||
$cidr .= '/32';
|
||||
}
|
||||
// $range is in IP/CIDR format eg 127.0.0.1/24
|
||||
list( $cidr, $netmask ) = explode('/', $cidr, 2);
|
||||
$range_decimal = ip2long($cidr);
|
||||
$ip_decimal = ip2long($ip);
|
||||
$wildcard_decimal = pow(2, ( 32 - $netmask)) - 1;
|
||||
$netmask_decimal = ~ $wildcard_decimal;
|
||||
return ( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given ipv6 address is in a given cidr
|
||||
* @param string $ip IP to check in IPV6 format
|
||||
* @param string $cidr CIDR netmask
|
||||
* @return boolean true if the IP is in this range, false otherwise.
|
||||
* @author MW. <https://stackoverflow.com/a/7952169>
|
||||
*/
|
||||
public static function ip6_in_cidr($ip, $cidr) {
|
||||
$address = inet_pton($ip);
|
||||
$subnetAddress = inet_pton(explode("/", $cidr)[0]);
|
||||
$subnetMask = explode("/", $cidr)[1];
|
||||
|
||||
$addr = str_repeat("f", $subnetMask / 4);
|
||||
switch ($subnetMask % 4) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
$addr .= "8";
|
||||
break;
|
||||
case 2:
|
||||
$addr .= "c";
|
||||
break;
|
||||
case 3:
|
||||
$addr .= "e";
|
||||
break;
|
||||
}
|
||||
$addr = str_pad($addr, 32, '0');
|
||||
$addr = pack("H*", $addr);
|
||||
|
||||
$binMask = $addr;
|
||||
return ($address & $binMask) == $subnetAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the REMOTE_ADDR is on Cloudflare's network.
|
||||
* @return boolean true if it is, otherwise false
|
||||
*/
|
||||
public static function validateCloudflare() {
|
||||
if (filter_var($_SERVER["REMOTE_ADDR"], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||
// Using IPv6
|
||||
$cloudflare_ips_v6 = [
|
||||
"2400:cb00::/32",
|
||||
"2405:8100::/32",
|
||||
"2405:b500::/32",
|
||||
"2606:4700::/32",
|
||||
"2803:f800::/32",
|
||||
"2c0f:f248::/32",
|
||||
"2a06:98c0::/29"
|
||||
];
|
||||
$valid = false;
|
||||
foreach ($cloudflare_ips_v6 as $cidr) {
|
||||
if ($this::ip6_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) {
|
||||
$valid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Using IPv4
|
||||
$cloudflare_ips_v4 = [
|
||||
"103.21.244.0/22",
|
||||
"103.22.200.0/22",
|
||||
"103.31.4.0/22",
|
||||
"104.16.0.0/12",
|
||||
"108.162.192.0/18",
|
||||
"131.0.72.0/22",
|
||||
"141.101.64.0/18",
|
||||
"162.158.0.0/15",
|
||||
"172.64.0.0/13",
|
||||
"173.245.48.0/20",
|
||||
"188.114.96.0/20",
|
||||
"190.93.240.0/20",
|
||||
"197.234.240.0/22",
|
||||
"198.41.128.0/17"
|
||||
];
|
||||
$valid = false;
|
||||
foreach ($cloudflare_ips_v4 as $cidr) {
|
||||
if ($this::ip4_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) {
|
||||
$valid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a good guess at the client's real IP address.
|
||||
*
|
||||
* @return string Client IP or `0.0.0.0` if we can't find anything
|
||||
*/
|
||||
public static function getClientIP() {
|
||||
// If CloudFlare is in the mix, we should use it.
|
||||
// Check if the request is actually from CloudFlare before trusting it.
|
||||
if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
|
||||
if ($this::validateCloudflare()) {
|
||||
return $_SERVER["HTTP_CF_CONNECTING_IP"];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_SERVER["REMOTE_ADDR"])) {
|
||||
return $_SERVER["REMOTE_ADDR"];
|
||||
}
|
||||
|
||||
return "0.0.0.0"; // This will not happen unless we aren't a web server
|
||||
}
|
||||
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
class Log {
|
||||
|
||||
/**
|
||||
*
|
||||
* @global $database
|
||||
* @param int/LogType $type Either an integer (as defined by the constants in class LogType) or a LogType object.
|
||||
* @param int/User $user Either a UID number or a User object.
|
||||
* @param string $data Extra data to include in the log, in addition to the timestamp, log type, user, and IP address.
|
||||
*/
|
||||
public static function insert($type, $user, string $data = "") {
|
||||
global $database;
|
||||
// find IP address
|
||||
$ip = IPUtils::getClientIP();
|
||||
if (gettype($type) == "object" && is_a($type, "LogType")) {
|
||||
$type = $type->getType();
|
||||
}
|
||||
|
||||
if (is_a($user, "User")) {
|
||||
$uid = $user->getUID();
|
||||
} else if (gettype($user) == "integer") {
|
||||
$uid = $user;
|
||||
} else {
|
||||
$uid = null;
|
||||
}
|
||||
|
||||
$database->insert("authlog", ['logtime' => date("Y-m-d H:i:s"), 'logtype' => $type, 'uid' => $uid, 'ip' => $ip, 'otherdata' => $data]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class LogType {
|
||||
|
||||
const LOGIN_OK = 1;
|
||||
const LOGIN_FAILED = 2;
|
||||
const PASSWORD_CHANGED = 3;
|
||||
const API_LOGIN_OK = 4;
|
||||
const API_LOGIN_FAILED = 5;
|
||||
const BAD_2FA = 6;
|
||||
const API_BAD_2FA = 7;
|
||||
const BAD_CAPTCHA = 8;
|
||||
const ADDED_2FA = 9;
|
||||
const REMOVED_2FA = 10;
|
||||
const LOGOUT = 11;
|
||||
const API_AUTH_OK = 12;
|
||||
const API_AUTH_FAILED = 13;
|
||||
const API_BAD_KEY = 14;
|
||||
const LOG_CLEARED = 15;
|
||||
const USER_REMOVED = 16;
|
||||
const USER_ADDED = 17;
|
||||
const USER_EDITED = 18;
|
||||
const MOBILE_LOGIN_OK = 19;
|
||||
const MOBILE_LOGIN_FAILED = 20;
|
||||
const MOBILE_BAD_KEY = 21;
|
||||
|
||||
private $type;
|
||||
|
||||
function __construct(int $type) {
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
public function getType(): int {
|
||||
return $type;
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
class Login {
|
||||
|
||||
const BAD_USERPASS = 1;
|
||||
const BAD_2FA = 2;
|
||||
const ACCOUNT_DISABLED = 3;
|
||||
const LOGIN_OK = 4;
|
||||
|
||||
public static function auth(string $username, string $password, string $twofa = ""): int {
|
||||
global $database;
|
||||
$username = strtolower($username);
|
||||
|
||||
$user = User::byUsername($username);
|
||||
|
||||
if (!$user->exists()) {
|
||||
return Login::BAD_USERPASS;
|
||||
}
|
||||
if (!$user->checkPassword($password)) {
|
||||
return Login::BAD_USERPASS;
|
||||
}
|
||||
|
||||
if ($user->has2fa()) {
|
||||
if (!$user->check2fa($twofa)) {
|
||||
return Login::BAD_2FA;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($user->getStatus()->get()) {
|
||||
case AccountStatus::TERMINATED:
|
||||
return Login::BAD_USERPASS;
|
||||
case AccountStatus::LOCKED_OR_DISABLED:
|
||||
return Login::ACCOUNT_DISABLED;
|
||||
case AccountStatus::NORMAL:
|
||||
default:
|
||||
return Login::LOGIN_OK;
|
||||
}
|
||||
|
||||
return Login::LOGIN_OK;
|
||||
}
|
||||
|
||||
public static function verifyCaptcha(string $session, string $answer, string $url): bool {
|
||||
$data = [
|
||||
'session_id' => $session,
|
||||
'answer_id' => $answer,
|
||||
'action' => "verify"
|
||||
];
|
||||
$options = [
|
||||
'http' => [
|
||||
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
|
||||
'method' => 'POST',
|
||||
'content' => http_build_query($data)
|
||||
]
|
||||
];
|
||||
$context = stream_context_create($options);
|
||||
$result = file_get_contents($url, false, $context);
|
||||
$resp = json_decode($result, TRUE);
|
||||
if (!$resp['result']) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
class LoginKey {
|
||||
|
||||
public static function generate(string $appname, $appicon = null): string {
|
||||
global $database;
|
||||
do {
|
||||
$code = base64_encode(random_bytes(32));
|
||||
} while ($database->has('userloginkeys', ['key' => $code]));
|
||||
|
||||
$database->insert('userloginkeys', ['key' => $code, 'expires' => date("Y-m-d H:i:s", time() + 600), 'appname' => $appname, 'appicon' => $appicon]);
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
public static function getuid(string $code): int {
|
||||
global $database;
|
||||
if (!$database->has('userloginkeys', ["AND" => ['key' => $code, 'uid[!]' => null]])) {
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
$uid = $database->get('userloginkeys', 'uid', ['key' => $code]);
|
||||
|
||||
return $uid;
|
||||
}
|
||||
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
class Notifications {
|
||||
|
||||
/**
|
||||
* Add a new notification.
|
||||
* @global $database
|
||||
* @param User $user
|
||||
* @param string $title
|
||||
* @param string $content
|
||||
* @param string $timestamp If left empty, the current date and time will be used.
|
||||
* @param string $url
|
||||
* @param bool $sensitive If true, the notification is marked as containing sensitive content, and the $content might be hidden on lockscreens and other non-secure places.
|
||||
* @return int The newly-created notification ID.
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function add(User $user, string $title, string $content, string $timestamp = "", string $url = "", bool $sensitive = false): int {
|
||||
global $database, $Strings;
|
||||
if ($user->exists()) {
|
||||
if (empty($title) || empty($content)) {
|
||||
throw new Exception($Strings->get("invalid parameters", false));
|
||||
}
|
||||
|
||||
$timestamp = date("Y-m-d H:i:s");
|
||||
if (!empty($timestamp)) {
|
||||
$timestamp = date("Y-m-d H:i:s", strtotime($timestamp));
|
||||
}
|
||||
|
||||
$database->insert('notifications', ['uid' => $user->getUID(), 'timestamp' => $timestamp, 'title' => $title, 'content' => $content, 'url' => $url, 'seen' => 0, 'sensitive' => $sensitive]);
|
||||
return $database->id() * 1;
|
||||
}
|
||||
throw new Exception($Strings->get("user does not exist", false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all notifications for a user.
|
||||
* @global $database
|
||||
* @param User $user
|
||||
* @param bool $all If false, only returns unseen notifications.
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function get(User $user, bool $all = true) {
|
||||
global $database, $Strings;
|
||||
if ($user->exists()) {
|
||||
if ($all) {
|
||||
$notifications = $database->select('notifications', ['notificationid (id)', 'timestamp', 'title', 'content', 'url', 'seen', 'sensitive'], ['uid' => $user->getUID(), 'ORDER' => ['seen', 'timestamp' => 'DESC']]);
|
||||
} else {
|
||||
$notifications = $database->select('notifications', ['notificationid (id)', 'timestamp', 'title', 'content', 'url', 'seen', 'sensitive'], ["AND" => ['uid' => $user->getUID(), 'seen' => 0], 'ORDER' => ['timestamp' => 'DESC']]);
|
||||
}
|
||||
for ($i = 0; $i < count($notifications); $i++) {
|
||||
$notifications[$i]['id'] = $notifications[$i]['id'] * 1;
|
||||
$notifications[$i]['seen'] = ($notifications[$i]['seen'] == "1" ? true : false);
|
||||
$notifications[$i]['sensitive'] = ($notifications[$i]['sensitive'] == "1" ? true : false);
|
||||
}
|
||||
return $notifications;
|
||||
}
|
||||
throw new Exception($Strings->get("user does not exist", false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the notification identified by $id as read.
|
||||
* @global $database
|
||||
* @global $Strings
|
||||
* @param User $user
|
||||
* @param int $id
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function read(User $user, int $id) {
|
||||
global $database, $Strings;
|
||||
if ($user->exists()) {
|
||||
if ($database->has('notifications', ['AND' => ['uid' => $user->getUID(), 'notificationid' => $id]])) {
|
||||
$database->update('notifications', ['seen' => 1], ['AND' => ['uid' => $user->getUID(), 'notificationid' => $id]]);
|
||||
return true;
|
||||
}
|
||||
throw new Exception($Strings->get("invalid parameters", false));
|
||||
}
|
||||
throw new Exception($Strings->get("user does not exist", false));
|
||||
}
|
||||
|
||||
public static function delete(User $user, int $id) {
|
||||
global $database, $Strings;
|
||||
if ($user->exists()) {
|
||||
if ($database->has('notifications', ['AND' => ['uid' => $user->getUID(), 'notificationid' => $id]])) {
|
||||
$database->delete('notifications', ['AND' => ['uid' => $user->getUID(), 'notificationid' => $id]]);
|
||||
return true;
|
||||
}
|
||||
throw new Exception($Strings->get("invalid parameters", false));
|
||||
}
|
||||
throw new Exception($Strings->get("user does not exist", false));
|
||||
}
|
||||
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
class RandomString {
|
||||
|
||||
/**
|
||||
* Generate a random string, using a cryptographically secure
|
||||
* pseudorandom number generator (random_int)
|
||||
*
|
||||
* From https://stackoverflow.com/a/31107425
|
||||
*
|
||||
* @param int $length How many characters do we want?
|
||||
* @param string $keyspace A string of all possible characters
|
||||
* to select from
|
||||
* @return string
|
||||
*/
|
||||
public static function generate(int $length, string $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'): string {
|
||||
$pieces = [];
|
||||
$max = mb_strlen($keyspace, '8bit') - 1;
|
||||
for ($i = 0; $i < $length; ++$i) {
|
||||
$pieces [] = $keyspace[random_int(0, $max)];
|
||||
}
|
||||
return implode('', $pieces);
|
||||
}
|
||||
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
class Session {
|
||||
|
||||
public static function start(User $user) {
|
||||
$_SESSION['username'] = $user->getUsername();
|
||||
$_SESSION['uid'] = $user->getUID();
|
||||
$_SESSION['email'] = $user->getEmail();
|
||||
$_SESSION['realname'] = $user->getName();
|
||||
$_SESSION['loggedin'] = true;
|
||||
}
|
||||
|
||||
}
|
@ -1,122 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides translated language strings.
|
||||
*/
|
||||
class Strings {
|
||||
|
||||
private $language = "en";
|
||||
private $strings = [];
|
||||
|
||||
public function __construct($language = "en") {
|
||||
if (!preg_match("/[a-zA-Z\_\-]+/", $language)) {
|
||||
throw new Exception("Invalid language code $language");
|
||||
}
|
||||
|
||||
$this->load("en");
|
||||
|
||||
if ($language == "en") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (file_exists(__DIR__ . "/../langs/$language/")) {
|
||||
$this->language = $language;
|
||||
$this->load($language);
|
||||
} else {
|
||||
trigger_error("Language $language could not be found.", E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all JSON files for the specified language.
|
||||
* @param string $language
|
||||
*/
|
||||
private function load(string $language) {
|
||||
$files = glob(__DIR__ . "/../langs/$language/*.json");
|
||||
foreach ($files as $file) {
|
||||
$strings = json_decode(file_get_contents($file), true);
|
||||
foreach ($strings as $key => $val) {
|
||||
if (array_key_exists($key, $this->strings)) {
|
||||
trigger_error("Language key \"$key\" is defined more than once.", E_USER_WARNING);
|
||||
}
|
||||
$this->strings[$key] = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add language strings dynamically.
|
||||
* @param array $strings ["key" => "value", ...]
|
||||
*/
|
||||
public function addStrings(array $strings) {
|
||||
foreach ($strings as $key => $val) {
|
||||
$this->strings[$key] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* I18N string getter. If the key isn't found, it outputs the key itself.
|
||||
* @param string $key
|
||||
* @param bool $echo True to echo the result, false to return it. Default is true.
|
||||
* @return string
|
||||
*/
|
||||
public function get(string $key, bool $echo = true): string {
|
||||
$str = $key;
|
||||
if (array_key_exists($key, $this->strings)) {
|
||||
$str = $this->strings[$key];
|
||||
} else {
|
||||
trigger_error("Language key \"$key\" does not exist in " . $this->language, E_USER_WARNING);
|
||||
}
|
||||
|
||||
if ($echo) {
|
||||
echo $str;
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* I18N string getter (with builder). If the key doesn't exist, outputs the key itself.
|
||||
* @param string $key
|
||||
* @param array $replace key-value array of replacements.
|
||||
* If the string value is "hello {abc}" and you give ["abc" => "123"], the
|
||||
* result will be "hello 123".
|
||||
* @param bool $echo True to echo the result, false to return it. Default is true.
|
||||
* @return string
|
||||
*/
|
||||
public function build(string $key, array $replace, bool $echo = true): string {
|
||||
$str = $key;
|
||||
if (array_key_exists($key, $this->strings)) {
|
||||
$str = $this->strings[$key];
|
||||
} else {
|
||||
trigger_error("Language key \"$key\" does not exist in " . $this->language, E_USER_WARNING);
|
||||
}
|
||||
|
||||
foreach ($replace as $find => $repl) {
|
||||
$str = str_replace("{" . $find . "}", $repl, $str);
|
||||
}
|
||||
|
||||
if ($echo) {
|
||||
echo $str;
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns a JSON key:value string for the supplied array of keys.
|
||||
* @param array $keys ["key1", "key2", ...]
|
||||
*/
|
||||
public function getJSON(array $keys): string {
|
||||
$strings = [];
|
||||
foreach ($keys as $k) {
|
||||
$strings[$k] = $this->get($k, false);
|
||||
}
|
||||
return json_encode($strings);
|
||||
}
|
||||
|
||||
}
|
329
lib/User.lib.php
329
lib/User.lib.php
@ -1,329 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
use Base32\Base32;
|
||||
use OTPHP\TOTP;
|
||||
|
||||
class User {
|
||||
|
||||
private $uid = null;
|
||||
private $username;
|
||||
private $passhash;
|
||||
private $email;
|
||||
private $realname;
|
||||
private $authsecret;
|
||||
private $has2fa = false;
|
||||
private $exists = false;
|
||||
private $apppasswords = [];
|
||||
|
||||
public function __construct(int $uid, string $username = "") {
|
||||
global $database;
|
||||
if ($database->has('accounts', ['AND' => ['uid' => $uid, 'deleted' => false]])) {
|
||||
$this->uid = $uid;
|
||||
$user = $database->get('accounts', ['username', 'password', 'email', 'realname', 'authsecret'], ['uid' => $uid]);
|
||||
$this->username = $user['username'];
|
||||
$this->passhash = $user['password'];
|
||||
$this->email = $user['email'];
|
||||
$this->realname = $user['realname'];
|
||||
$this->authsecret = $user['authsecret'];
|
||||
$this->has2fa = !empty($user['authsecret']);
|
||||
$this->exists = true;
|
||||
$this->apppasswords = $database->select('apppasswords', 'hash', ['uid' => $this->uid]);
|
||||
} else {
|
||||
$this->uid = $uid;
|
||||
$this->username = $username;
|
||||
}
|
||||
}
|
||||
|
||||
public static function byUsername(string $username): User {
|
||||
global $database;
|
||||
$username = strtolower($username);
|
||||
if ($database->has('accounts', ['AND' => ['username' => $username, 'deleted' => false]])) {
|
||||
$uid = $database->get('accounts', 'uid', ['username' => $username]);
|
||||
return new self($uid * 1);
|
||||
}
|
||||
return new self(-1, $username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a user to the system. /!\ Assumes input is OK /!\
|
||||
* @param string $username Username, saved in lowercase.
|
||||
* @param string $password Password, will be hashed before saving.
|
||||
* @param string $realname User's real legal name
|
||||
* @param string $email User's email address.
|
||||
* @param string $phone1 Phone number #1
|
||||
* @param string $phone2 Phone number #2
|
||||
* @param int $type Account type
|
||||
* @return int The new user's ID number in the database.
|
||||
*/
|
||||
public static function add(string $username, string $password, string $realname, string $email = null, string $phone1 = "", string $phone2 = "", int $type = 1): int {
|
||||
global $database;
|
||||
$database->insert('accounts', [
|
||||
'username' => strtolower($username),
|
||||
'password' => (is_null($password) ? null : password_hash($password, PASSWORD_BCRYPT)),
|
||||
'realname' => $realname,
|
||||
'email' => $email,
|
||||
'phone1' => $phone1,
|
||||
'phone2' => $phone2,
|
||||
'acctstatus' => 1,
|
||||
'accttype' => $type
|
||||
]);
|
||||
return $database->id();
|
||||
}
|
||||
|
||||
public function exists(): bool {
|
||||
return $this->exists;
|
||||
}
|
||||
|
||||
public function has2fa(): bool {
|
||||
return $this->has2fa;
|
||||
}
|
||||
|
||||
function getUsername() {
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
function getUID() {
|
||||
return $this->uid;
|
||||
}
|
||||
|
||||
function getEmail() {
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
function getName() {
|
||||
return $this->realname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the given plaintext password against the stored hash.
|
||||
* @param string $password
|
||||
* @return bool
|
||||
*/
|
||||
function checkPassword(string $password): bool {
|
||||
return password_verify($password, $this->passhash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the given password against the user's app passwords.
|
||||
* @param string $apppassword
|
||||
* @return bool
|
||||
*/
|
||||
function checkAppPassword(string $apppassword): bool {
|
||||
foreach ($this->apppasswords as $hash) {
|
||||
if (password_verify($apppassword, $hash)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the user's password.
|
||||
* @global $database $database
|
||||
* @param string $old The current password
|
||||
* @param string $new The new password
|
||||
* @param string $new2 New password again
|
||||
* @throws PasswordMatchException
|
||||
* @throws PasswordMismatchException
|
||||
* @throws IncorrectPasswordException
|
||||
* @throws WeakPasswordException
|
||||
*/
|
||||
function changePassword(string $old, string $new, string $new2) {
|
||||
global $database, $SETTINGS;
|
||||
if ($old == $new) {
|
||||
throw new PasswordMatchException();
|
||||
}
|
||||
if ($new != $new2) {
|
||||
throw new PasswordMismatchException();
|
||||
}
|
||||
|
||||
if (!$this->checkPassword($old)) {
|
||||
throw new IncorrectPasswordException();
|
||||
}
|
||||
|
||||
require_once __DIR__ . "/worst_passwords.php";
|
||||
|
||||
$passrank = checkWorst500List($new);
|
||||
if ($passrank !== FALSE) {
|
||||
throw new WeakPasswordException();
|
||||
}
|
||||
if (strlen($new) < $SETTINGS['min_password_length']) {
|
||||
throw new WeakPasswordException();
|
||||
}
|
||||
|
||||
$database->update('accounts', ['password' => password_hash($new, PASSWORD_DEFAULT), 'acctstatus' => 1], ['uid' => $this->uid]);
|
||||
Log::insert(LogType::PASSWORD_CHANGED, $this);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function check2fa(string $code): bool {
|
||||
if (!$this->has2fa) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$totp = new TOTP(null, $this->authsecret);
|
||||
$time = time();
|
||||
if ($totp->verify($code, $time)) {
|
||||
return true;
|
||||
}
|
||||
if ($totp->verify($code, $time - 30)) {
|
||||
return true;
|
||||
}
|
||||
if ($totp->verify($code, $time + 30)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a TOTP secret for the given user.
|
||||
* @return string OTP provisioning URI (for generating a QR code)
|
||||
*/
|
||||
function generate2fa(): string {
|
||||
global $SETTINGS;
|
||||
$secret = random_bytes(20);
|
||||
$encoded_secret = Base32::encode($secret);
|
||||
$totp = new TOTP((empty($this->email) ? $this->realname : $this->email), $encoded_secret);
|
||||
$totp->setIssuer($SETTINGS['system_name']);
|
||||
return $totp->getProvisioningUri();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a TOTP secret for the user.
|
||||
* @global $database $database
|
||||
* @param string $username
|
||||
* @param string $secret
|
||||
*/
|
||||
function save2fa(string $secret) {
|
||||
global $database;
|
||||
$database->update('accounts', ['authsecret' => $secret], ['username' => $this->username]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given username has the given permission (or admin access)
|
||||
* @global $database $database
|
||||
* @param string $code
|
||||
* @return boolean TRUE if the user has the permission (or admin access), else FALSE
|
||||
*/
|
||||
function hasPermission(string $code): bool {
|
||||
global $database;
|
||||
return $database->has('assigned_permissions', [
|
||||
'[>]permissions' => [
|
||||
'permid' => 'permid'
|
||||
]
|
||||
], ['AND' => ['OR' => ['permcode #code' => $code, 'permcode #admin' => 'ADMIN'], 'uid' => $this->uid]]) === TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the account status.
|
||||
* @return \AccountStatus
|
||||
*/
|
||||
function getStatus(): AccountStatus {
|
||||
global $database;
|
||||
$statuscode = $database->get('accounts', 'acctstatus', ['uid' => $this->uid]);
|
||||
return new AccountStatus($statuscode);
|
||||
}
|
||||
|
||||
function sendAlertEmail(string $appname = null) {
|
||||
global $SETTINGS, $Strings;
|
||||
if (is_null($appname)) {
|
||||
$appname = $SETTINGS['site_title'];
|
||||
}
|
||||
if (empty($SETTINGS["email"]["admin_email"]) || filter_var($SETTINGS["email"]["admin_email"], FILTER_VALIDATE_EMAIL) === FALSE) {
|
||||
return "invalid_to_email";
|
||||
}
|
||||
if (empty($SETTINGS["email"]["from"]) || filter_var($SETTINGS["email"]["from"], FILTER_VALIDATE_EMAIL) === FALSE) {
|
||||
return "invalid_from_email";
|
||||
}
|
||||
|
||||
$mail = new PHPMailer;
|
||||
|
||||
if ($SETTINGS['debug']) {
|
||||
$mail->SMTPDebug = 2;
|
||||
}
|
||||
|
||||
if ($SETTINGS['email']['use_smtp']) {
|
||||
$mail->isSMTP();
|
||||
$mail->Host = $SETTINGS['email']['host'];
|
||||
$mail->SMTPAuth = $SETTINGS['email']['auth'];
|
||||
$mail->Username = $SETTINGS['email']['user'];
|
||||
$mail->Password = $SETTINGS['email']['password'];
|
||||
$mail->SMTPSecure = $SETTINGS['email']['secure'];
|
||||
$mail->Port = $SETTINGS['email']['port'];
|
||||
if ($SETTINGS['email']['allow_invalid_certificate']) {
|
||||
$mail->SMTPOptions = array(
|
||||
'ssl' => array(
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => false,
|
||||
'allow_self_signed' => true
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$mail->setFrom($SETTINGS["email"]["from"], 'Account Alerts');
|
||||
$mail->addAddress($SETTINGS["email"]["admin_email"], "System Admin");
|
||||
$mail->isHTML(false);
|
||||
$mail->Subject = $Strings->get("admin alert email subject", false);
|
||||
$mail->Body = $Strings->build("admin alert email message", ["username" => $this->username, "datetime" => date("Y-m-d H:i:s"), "ipaddr" => IPUtils::getClientIP(), "appname" => $appname], false);
|
||||
|
||||
if (!$mail->send()) {
|
||||
return $mail->ErrorInfo;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class AccountStatus {
|
||||
|
||||
const NORMAL = 1;
|
||||
const LOCKED_OR_DISABLED = 2;
|
||||
const CHANGE_PASSWORD = 3;
|
||||
const TERMINATED = 4;
|
||||
const ALERT_ON_ACCESS = 5;
|
||||
|
||||
private $status;
|
||||
|
||||
public function __construct(int $status) {
|
||||
$this->status = $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the account status/state as an integer.
|
||||
* @return int
|
||||
*/
|
||||
public function get(): int {
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the account status/state as a string representation.
|
||||
* @return string
|
||||
*/
|
||||
public function getString(): string {
|
||||
switch ($this->status) {
|
||||
case self::NORMAL:
|
||||
return "NORMAL";
|
||||
case self::LOCKED_OR_DISABLED:
|
||||
return "LOCKED_OR_DISABLED";
|
||||
case self::CHANGE_PASSWORD:
|
||||
return "CHANGE_PASSWORD";
|
||||
case self::TERMINATED:
|
||||
return "TERMINATED";
|
||||
case self::ALERT_ON_ACCESS:
|
||||
return "ALERT_ON_ACCESS";
|
||||
default:
|
||||
return "OTHER_" . $this->status;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
510
lib/login.php
Normal file
510
lib/login.php
Normal file
@ -0,0 +1,510 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Authentication and account functions
|
||||
*/
|
||||
use Base32\Base32;
|
||||
use OTPHP\TOTP;
|
||||
use LdapTools\LdapManager;
|
||||
use LdapTools\Object\LdapObjectType;
|
||||
|
||||
$ldap = new LdapManager($ldap_config);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Account handling //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Add a user to the system. /!\ Assumes input is OK /!\
|
||||
* @param string $username Username, saved in lowercase.
|
||||
* @param string $password Password, will be hashed before saving.
|
||||
* @param string $realname User's real legal name
|
||||
* @param string $email User's email address.
|
||||
* @param string $phone1 Phone number #1
|
||||
* @param string $phone2 Phone number #2
|
||||
* @param string $type Account type
|
||||
* @return int The new user's ID number in the database.
|
||||
*/
|
||||
function adduser($username, $password, $realname, $email = null, $phone1 = "", $phone2 = "", $type) {
|
||||
global $database;
|
||||
$database->insert('accounts', [
|
||||
'username' => strtolower($username),
|
||||
'password' => (is_null($password) ? null : encryptPassword($password)),
|
||||
'realname' => $realname,
|
||||
'email' => $email,
|
||||
'phone1' => $phone1,
|
||||
'phone2' => $phone2,
|
||||
'acctstatus' => 1,
|
||||
'accttype' => $type
|
||||
]);
|
||||
//var_dump($database->error());
|
||||
return $database->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the password for the current user.
|
||||
* @global $database $database
|
||||
* @global LdapManager $ldap
|
||||
* @param string $old The current password
|
||||
* @param string $new The new password
|
||||
* @param string $new2 New password again
|
||||
* @param [string] $error If the function returns false, this will have an array
|
||||
* with a message ID from `lang/messages.php` and (depending on the message) an
|
||||
* extra string for that message.
|
||||
* @return boolean true if the password is changed, else false
|
||||
*/
|
||||
function change_password($old, $new, $new2, &$error) {
|
||||
global $database, $ldap;
|
||||
// make sure the new password isn't the same as the current one
|
||||
if ($old == $new) {
|
||||
$error = ["passwords_same"];
|
||||
return false;
|
||||
}
|
||||
// Make sure the new passwords are the same
|
||||
if ($new != $new2) {
|
||||
$error = ["new_password_mismatch"];
|
||||
return false;
|
||||
}
|
||||
// check the current password
|
||||
$login_ok = authenticate_user($_SESSION['username'], $old, $errmsg, $errcode);
|
||||
// Allow login if the error is due to expired password
|
||||
if (!$login_ok && ($errcode == LdapTools\Connection\ADResponseCodes::ACCOUNT_PASSWORD_EXPIRED || $errcode == LdapTools\Connection\ADResponseCodes::ACCOUNT_PASSWORD_MUST_CHANGE)) {
|
||||
$login_ok = true;
|
||||
}
|
||||
if ($login_ok) {
|
||||
// Check the new password and make sure it's not stupid
|
||||
require_once __DIR__ . "/worst_passwords.php";
|
||||
$passrank = checkWorst500List($new);
|
||||
if ($passrank !== FALSE) {
|
||||
$error = ["password_500", $passrank];
|
||||
return false;
|
||||
}
|
||||
if (strlen($new) < MIN_PASSWORD_LENGTH) {
|
||||
$error = ["weak_password"];
|
||||
return false;
|
||||
}
|
||||
|
||||
// Figure out how to change the password, then do it
|
||||
$acctloc = account_location($_SESSION['username']);
|
||||
if ($acctloc == "LOCAL") {
|
||||
$database->update('accounts', ['password' => encryptPassword($VARS['newpass'])], ['uid' => $_SESSION['uid']]);
|
||||
$_SESSION['password'] = $VARS['newpass'];
|
||||
insertAuthLog(3, $_SESSION['uid']);
|
||||
return true;
|
||||
} else if ($acctloc == "LDAP") {
|
||||
try {
|
||||
$repository = $ldap->getRepository(LdapObjectType::USER);
|
||||
$user = $repository->findOneByUsername($_SESSION['username']);
|
||||
$user->setPassword($VARS['newpass']);
|
||||
$user->setpasswordMustChange(false);
|
||||
$ldap->persist($user);
|
||||
insertAuthLog(3, $_SESSION['uid']);
|
||||
$_SESSION['password'] = $VARS['newpass'];
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
// Stupid password complexity BS error
|
||||
if (strpos($e->getMessage(), "DSID-031A11E5") !== FALSE) {
|
||||
$error = ["password_complexity"];
|
||||
return false;
|
||||
}
|
||||
$error = ["ldap_error", $e->getMessage()];
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$error = ["account_state_error"];
|
||||
return false;
|
||||
}
|
||||
$error = ["old_password_mismatch"];
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get where a user's account actually is.
|
||||
* @param string $username
|
||||
* @return string "LDAP", "LOCAL", "LDAP_ONLY", or "NONE".
|
||||
*/
|
||||
function account_location($username) {
|
||||
global $database;
|
||||
$username = strtolower($username);
|
||||
$user_exists = user_exists_local($username);
|
||||
if (!$user_exists && !LDAP_ENABLED) {
|
||||
return false;
|
||||
}
|
||||
if ($user_exists) {
|
||||
$userinfo = $database->select('accounts', ['password'], ['username' => $username])[0];
|
||||
// if password empty, it's an LDAP user
|
||||
if (is_empty($userinfo['password']) && LDAP_ENABLED) {
|
||||
return "LDAP";
|
||||
} else if (is_empty($userinfo['password']) && !LDAP_ENABLED) {
|
||||
return "NONE";
|
||||
} else {
|
||||
return "LOCAL";
|
||||
}
|
||||
} else {
|
||||
if (user_exists_ldap($username)) {
|
||||
return "LDAP_ONLY";
|
||||
} else {
|
||||
return "NONE";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the given credentials against the database.
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return boolean True if OK, else false
|
||||
*/
|
||||
function authenticate_user($username, $password, &$errormsg, &$errorcode) {
|
||||
global $database;
|
||||
global $ldap;
|
||||
$username = strtolower($username);
|
||||
if (is_empty($username) || is_empty($password)) {
|
||||
return false;
|
||||
}
|
||||
$loc = account_location($username, $password);
|
||||
switch ($loc) {
|
||||
case "LOCAL":
|
||||
$hash = $database->select('accounts', ['password'], ['username' => $username, "LIMIT" => 1])[0]['password'];
|
||||
return (comparePassword($password, $hash));
|
||||
case "LDAP":
|
||||
return authenticate_user_ldap($username, $password, $errormsg, $errorcode) === TRUE;
|
||||
case "LDAP_ONLY":
|
||||
// Authenticate with LDAP and create database account
|
||||
try {
|
||||
if (authenticate_user_ldap($username, $password, $errormsg, $errorcode) === TRUE) {
|
||||
$user = $ldap->getRepository('user')->findOneByUsername($username);
|
||||
|
||||
adduser($user->getUsername(), null, $user->getName(), ($user->hasEmailAddress() ? $user->getEmailAddress() : null), "", "", 2);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (Exception $e) {
|
||||
$errormsg = $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function user_exists($username) {
|
||||
return account_location($username) !== "NONE";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a username exists in the local database.
|
||||
* @param String $username
|
||||
*/
|
||||
function user_exists_local($username) {
|
||||
global $database;
|
||||
$username = strtolower($username);
|
||||
return $database->has('accounts', ['username' => $username, "LIMIT" => QUERY_LIMIT]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the account status: NORMAL, TERMINATED, LOCKED_OR_DISABLED,
|
||||
* CHANGE_PASSWORD, ALERT_ON_ACCESS, or OTHER
|
||||
* @global $database $database
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return string
|
||||
*/
|
||||
function get_account_status($username, &$error) {
|
||||
global $database;
|
||||
$username = strtolower($username);
|
||||
$loc = account_location($username);
|
||||
if ($loc == "LOCAL") {
|
||||
$statuscode = $database->select('accounts', [
|
||||
'[>]acctstatus' => [
|
||||
'acctstatus' => 'statusid'
|
||||
]
|
||||
], [
|
||||
'accounts.acctstatus',
|
||||
'acctstatus.statuscode'
|
||||
], [
|
||||
'username' => $username,
|
||||
"LIMIT" => 1
|
||||
]
|
||||
)[0]['statuscode'];
|
||||
return $statuscode;
|
||||
} else if ($loc == "LDAP" || $loc == "LDAP_ONLY") {
|
||||
return get_account_status_ldap($username, $error);
|
||||
} else {
|
||||
// account isn't setup properly
|
||||
return "OTHER";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given username has the given permission (or admin access)
|
||||
* @global $database $database
|
||||
* @param string $username
|
||||
* @param string $permcode
|
||||
* @return boolean TRUE if the user has the permission (or admin access), else FALSE
|
||||
*/
|
||||
function account_has_permission($username, $permcode) {
|
||||
global $database;
|
||||
return $database->has('assigned_permissions', [
|
||||
'[>]accounts' => [
|
||||
'uid' => 'uid'
|
||||
],
|
||||
'[>]permissions' => [
|
||||
'permid' => 'permid'
|
||||
]
|
||||
], ['AND' => ['OR' => ['permcode #code' => $permcode, 'permcode #admin' => 'ADMIN'], 'username' => $username]]) === TRUE;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Login handling //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Setup $_SESSION values to log in a user
|
||||
* @param string $username
|
||||
*/
|
||||
function doLoginUser($username, $password) {
|
||||
global $database;
|
||||
$username = strtolower($username);
|
||||
$userinfo = $database->select('accounts', ['email', 'uid', 'realname'], ['username' => $username])[0];
|
||||
$_SESSION['username'] = $username;
|
||||
$_SESSION['uid'] = $userinfo['uid'];
|
||||
$_SESSION['email'] = $userinfo['email'];
|
||||
$_SESSION['realname'] = $userinfo['realname'];
|
||||
$_SESSION['password'] = $password; // needed for accessing data in other apps
|
||||
$_SESSION['loggedin'] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an alert email to the system admin
|
||||
*
|
||||
* Used when an account with the status ALERT_ON_ACCESS logs in
|
||||
* @param String $username the account username
|
||||
*/
|
||||
function sendLoginAlertEmail($username) {
|
||||
// TODO: add email code
|
||||
}
|
||||
|
||||
function insertAuthLog($type, $uid = null, $data = "") {
|
||||
global $database;
|
||||
// find IP address
|
||||
$ip = "";
|
||||
if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
|
||||
$ip = $_SERVER["HTTP_CF_CONNECTING_IP"];
|
||||
} else if (isset($_SERVER["HTTP_CLIENT_IP"])) {
|
||||
$ip = $_SERVER["HTTP_CLIENT_IP"];
|
||||
} else if (isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
|
||||
$ip = $_SERVER["HTTP_X_FORWARDED_FOR"];
|
||||
} else if (isset($_SERVER["HTTP_X_FORWARDED"])) {
|
||||
$ip = $_SERVER["HTTP_X_FORWARDED"];
|
||||
} else if (isset($_SERVER["HTTP_FORWARDED_FOR"])) {
|
||||
$ip = $_SERVER["HTTP_FORWARDED_FOR"];
|
||||
} else if (isset($_SERVER["HTTP_FORWARDED"])) {
|
||||
$ip = $_SERVER["HTTP_FORWARDED"];
|
||||
} else if (isset($_SERVER["REMOTE_ADDR"])) {
|
||||
$ip = $_SERVER["REMOTE_ADDR"];
|
||||
} else {
|
||||
$ip = "NOT FOUND";
|
||||
}
|
||||
$database->insert("authlog", ['logtime' => date("Y-m-d H:i:s"), 'logtype' => $type, 'uid' => $uid, 'ip' => $ip, 'otherdata' => $data]);
|
||||
}
|
||||
|
||||
function verifyReCaptcha($response) {
|
||||
try {
|
||||
$client = new GuzzleHttp\Client();
|
||||
|
||||
$response = $client
|
||||
->request('POST', "https://www.google.com/recaptcha/api/siteverify", [
|
||||
'form_params' => [
|
||||
'secret' => RECAPTCHA_SECRET_KEY,
|
||||
'response' => $response
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() != 200) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$resp = json_decode($response->getBody(), TRUE);
|
||||
if ($resp['success'] === true) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// LDAP handling //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Checks the given credentials against the LDAP server.
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return mixed True if OK, else false or the error code from the server
|
||||
*/
|
||||
function authenticate_user_ldap($username, $password, &$errormsg, &$errorcode) {
|
||||
global $ldap;
|
||||
if (is_empty($username) || is_empty($password)) {
|
||||
return false;
|
||||
}
|
||||
$username = strtolower($username);
|
||||
try {
|
||||
$msg = "";
|
||||
$code = 0;
|
||||
if ($ldap->authenticate($username, $password, $msg, $code) === TRUE) {
|
||||
$errormsg = $msg;
|
||||
$errorcode = $code;
|
||||
return true;
|
||||
} else {
|
||||
$errormsg = $msg;
|
||||
$errorcode = $code;
|
||||
return $msg;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$errormsg = $e->getMessage();
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a username exists on the LDAP server.
|
||||
* @global type $ldap_config
|
||||
* @param type $username
|
||||
* @return boolean true if yes, else false
|
||||
*/
|
||||
function user_exists_ldap($username) {
|
||||
global $ldap;
|
||||
try {
|
||||
$username = strtolower($username);
|
||||
$lqb = $ldap->buildLdapQuery();
|
||||
$result = $lqb->fromUsers()
|
||||
->where(['username' => $username])
|
||||
->getLdapQuery()
|
||||
->getResult();
|
||||
if (count($result) > 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function get_account_status_ldap($username, &$error) {
|
||||
global $ldap;
|
||||
try {
|
||||
$username = strtolower($username);
|
||||
$normal = $ldap->buildLdapQuery()
|
||||
->fromUsers()
|
||||
->where(['enabled' => true, 'passwordMustChange' => false, 'locked' => false, 'disabled' => false, 'username' => $username])
|
||||
->getLdapQuery()
|
||||
->getResult();
|
||||
if (count($normal) == 1) {
|
||||
return "NORMAL";
|
||||
}
|
||||
$disabled = $ldap->buildLdapQuery()
|
||||
->fromUsers()
|
||||
->where(['disabled' => true, 'username' => $username])
|
||||
->getLdapQuery()
|
||||
->getResult();
|
||||
$locked = $ldap->buildLdapQuery()
|
||||
->fromUsers()
|
||||
->where(['locked' => true, 'username' => $username])
|
||||
->getLdapQuery()
|
||||
->getResult();
|
||||
if (count($disabled) == 1 || count($locked) == 1) {
|
||||
return "LOCKED_OR_DISABLED";
|
||||
}
|
||||
$passwordExpired = $ldap->buildLdapQuery()
|
||||
->fromUsers()
|
||||
->where(['passwordMustChange' => true, 'username' => $username])
|
||||
->getLdapQuery()
|
||||
->getResult();
|
||||
if (count($passwordExpired) == 1) {
|
||||
return "CHANGE_PASSWORD";
|
||||
}
|
||||
$other = $ldap->buildLdapQuery()
|
||||
->fromUsers()
|
||||
->where(['username' => $username])
|
||||
->getLdapQuery()
|
||||
->getResult();
|
||||
if (count($other) == 0) {
|
||||
return false;
|
||||
} else {
|
||||
return "OTHER";
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$error = $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 2-factor authentication //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Check if a user has TOTP setup
|
||||
* @global $database $database
|
||||
* @param string $username
|
||||
* @return boolean true if TOTP secret exists, else false
|
||||
*/
|
||||
function userHasTOTP($username) {
|
||||
global $database;
|
||||
$username = strtolower($username);
|
||||
$secret = $database->select('accounts', 'authsecret', ['username' => $username])[0];
|
||||
if (is_empty($secret)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a TOTP secret for the given user.
|
||||
* @param string $username
|
||||
* @return string OTP provisioning URI (for generating a QR code)
|
||||
*/
|
||||
function newTOTP($username) {
|
||||
global $database;
|
||||
$username = strtolower($username);
|
||||
$secret = random_bytes(20);
|
||||
$encoded_secret = Base32::encode($secret);
|
||||
$userdata = $database->select('accounts', ['email', 'authsecret', 'realname'], ['username' => $username])[0];
|
||||
$totp = new TOTP((is_null($userdata['email']) ? $userdata['realname'] : $userdata['email']), $encoded_secret);
|
||||
$totp->setIssuer(SYSTEM_NAME);
|
||||
return $totp->getProvisioningUri();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a TOTP secret for the user.
|
||||
* @global $database $database
|
||||
* @param string $username
|
||||
* @param string $secret
|
||||
*/
|
||||
function saveTOTP($username, $secret) {
|
||||
global $database;
|
||||
$username = strtolower($username);
|
||||
$database->update('accounts', ['authsecret' => $secret], ['username' => $username]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a TOTP multiauth code
|
||||
* @global $database
|
||||
* @param string $username
|
||||
* @param int $code
|
||||
* @return boolean true if it's legit, else false
|
||||
*/
|
||||
function verifyTOTP($username, $code) {
|
||||
global $database;
|
||||
$username = strtolower($username);
|
||||
$userdata = $database->select('accounts', ['email', 'authsecret'], ['username' => $username])[0];
|
||||
if (is_empty($userdata['authsecret'])) {
|
||||
return false;
|
||||
}
|
||||
$totp = new TOTP(null, $userdata['authsecret']);
|
||||
return $totp->verify($code);
|
||||
}
|
@ -2,9 +2,6 @@
|
||||
/*
|
||||
* 500 most common passwords, to be used in stopping idiots from having really bad passwords.
|
||||
* Source: https://github.com/danielmiessler/SecLists/blob/master/Passwords/500-worst-passwords.txt
|
||||
*
|
||||
* This file is licensed under the Creative Commons Attribution ShareAlike 3.0 License.
|
||||
* https://creativecommons.org/licenses/by-sa/3.0/
|
||||
*/
|
||||
|
||||
|
||||
|
169
login/index.php
169
login/index.php
@ -1,169 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . "/../required.php";
|
||||
|
||||
|
||||
if (empty($_GET['code']) || empty($_GET['redirect'])) {
|
||||
die("Bad request.");
|
||||
}
|
||||
|
||||
// Delete old keys to keep the table small and tidy
|
||||
$database->delete("userloginkeys", ["expires[<]" => date("Y-m-d H:i:s")]);
|
||||
|
||||
if (!$database->has("userloginkeys", ["AND" => ["key" => $_GET["code"]], "expires[>]" => date("Y-m-d H:i:s"), "uid" => null])) {
|
||||
header("Location: $_GET[redirect]");
|
||||
die("Invalid auth code.");
|
||||
}
|
||||
|
||||
$APPINFO = $database->get("userloginkeys", ["appname", "appicon"], ["key" => $_GET["code"]]);
|
||||
$APPNAME = $APPINFO["appname"];
|
||||
$APPICON = $APPINFO["appicon"];
|
||||
|
||||
if (empty($_SESSION['thisstep'])) {
|
||||
$_SESSION['thisstep'] = "username";
|
||||
}
|
||||
|
||||
if (!empty($_GET['reset'])) {
|
||||
$_SESSION['thisstep'] = "username";
|
||||
$_SESSION['check'] = "";
|
||||
header("Location: ./?code=$_GET[code]&redirect=$_GET[redirect]");
|
||||
}
|
||||
|
||||
$error = "";
|
||||
|
||||
function sendUserBack($code, $url, $uid) {
|
||||
global $database;
|
||||
$_SESSION['check'] = null;
|
||||
$_SESSION['thisstep'] = null;
|
||||
$_SESSION['login_uid'] = null;
|
||||
$_SESSION['login_pwd'] = null;
|
||||
$database->update("userloginkeys", ["uid" => $uid], ["key" => $code]);
|
||||
Log::insert(LogType::LOGIN_OK, $uid);
|
||||
header("Location: $url");
|
||||
die("<a href=\"" . htmlspecialchars($url) . "\">Click here</a>");
|
||||
}
|
||||
|
||||
if (!empty($_SESSION['check'])) {
|
||||
switch ($_SESSION['check']) {
|
||||
case "username":
|
||||
if (empty($_POST['username'])) {
|
||||
$_SESSION['thisstep'] = "username";
|
||||
break;
|
||||
}
|
||||
$user = User::byUsername($_POST['username']);
|
||||
if ($user->exists()) {
|
||||
$_SESSION['login_uid'] = $user->getUID();
|
||||
switch ($user->getStatus()->get()) {
|
||||
case AccountStatus::LOCKED_OR_DISABLED:
|
||||
$error = $Strings->get("account locked", false);
|
||||
break;
|
||||
case AccountStatus::TERMINATED:
|
||||
$error = $Strings->get("account terminated", false);
|
||||
break;
|
||||
case AccountStatus::ALERT_ON_ACCESS:
|
||||
$mail_resp = $user->sendAlertEmail();
|
||||
case AccountStatus::NORMAL:
|
||||
$_SESSION['thisstep'] = "password";
|
||||
break;
|
||||
case AccountStatus::CHANGE_PASSWORD:
|
||||
$_SESSION['thisstep'] = "change_password";
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$error = $Strings->get("Username not found.", false);
|
||||
Log::insert(LogType::LOGIN_FAILED, null, "Username: " . $user->getUsername());
|
||||
}
|
||||
break;
|
||||
case "password":
|
||||
if (empty($_POST['password'])) {
|
||||
$_SESSION['thisstep'] = "password";
|
||||
break;
|
||||
}
|
||||
if (empty($_SESSION['login_uid'])) {
|
||||
$_SESSION['thisstep'] = "username";
|
||||
break;
|
||||
}
|
||||
$user = new User($_SESSION['login_uid']);
|
||||
if ($user->checkPassword($_POST['password'])) {
|
||||
$_SESSION['login_pwd'] = true;
|
||||
if ($user->has2fa()) {
|
||||
$_SESSION['thisstep'] = "totp";
|
||||
} else {
|
||||
sendUserBack($_GET['code'], $_GET['redirect'], $_SESSION['login_uid']);
|
||||
}
|
||||
} else {
|
||||
$error = $Strings->get("Password incorrect.", false);
|
||||
if ($user->checkAppPassword($_POST['password'])) {
|
||||
$error = $Strings->get("App passwords are not allowed here.", false);
|
||||
}
|
||||
Log::insert(LogType::LOGIN_FAILED, $user);
|
||||
}
|
||||
break;
|
||||
case "change_password":
|
||||
if (empty($_POST['oldpassword']) || empty($_POST['newpassword']) || empty($_POST['newpassword2'])) {
|
||||
$_SESSION['thisstep'] = "change_password";
|
||||
$error = $Strings->get("Fill in all three boxes.", false);
|
||||
break;
|
||||
}
|
||||
|
||||
$user = new User($_SESSION['login_uid']);
|
||||
try {
|
||||
$result = $user->changePassword($_POST['oldpassword'], $_POST['newpassword'], $_POST['newpassword2']);
|
||||
|
||||
if ($result === TRUE) {
|
||||
if ($user->has2fa()) {
|
||||
$_SESSION['thisstep'] = "totp";
|
||||
} else {
|
||||
sendUserBack($_GET['code'], $_GET['redirect'], $_SESSION['login_uid']);
|
||||
}
|
||||
}
|
||||
} catch (PasswordMatchException $e) {
|
||||
$error = $Strings->get(MESSAGES["passwords_same"]["string"], false);
|
||||
} catch (PasswordMismatchException $e) {
|
||||
$error = $Strings->get(MESSAGES["new_password_mismatch"]["string"], false);
|
||||
} catch (IncorrectPasswordException $e) {
|
||||
$error = $Strings->get(MESSAGES["old_password_mismatch"]["string"], false);
|
||||
} catch (WeakPasswordException $e) {
|
||||
$error = $Strings->get(MESSAGES["weak_password"]["string"], false);
|
||||
}
|
||||
break;
|
||||
case "totp":
|
||||
if (empty($_POST['totp']) || empty($_SESSION['login_uid'])) {
|
||||
$_SESSION['thisstep'] = "username";
|
||||
break;
|
||||
}
|
||||
$user = new User($_SESSION['login_uid']);
|
||||
if ($user->check2fa($_POST['totp'])) {
|
||||
sendUserBack($_GET['code'], $_GET['redirect'], $_SESSION['login_uid']);
|
||||
} else {
|
||||
$error = $Strings->get("Code incorrect.", false);
|
||||
Log::insert(LogType::BAD_2FA, null, "Username: " . $user->getUsername());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
include __DIR__ . "/parts/header.php";
|
||||
|
||||
switch ($_SESSION['thisstep']) {
|
||||
case "username":
|
||||
require __DIR__ . "/parts/username.php";
|
||||
break;
|
||||
case "password":
|
||||
require __DIR__ . "/parts/password.php";
|
||||
break;
|
||||
case "change_password":
|
||||
require __DIR__ . "/parts/change_password.php";
|
||||
break;
|
||||
case "totp":
|
||||
require __DIR__ . "/parts/totp.php";
|
||||
break;
|
||||
}
|
||||
|
||||
include __DIR__ . "/parts/footer.php";
|
@ -1,51 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$_SESSION['check'] = "change_password";
|
||||
$username = (new User($_SESSION['login_uid']))->getUsername();
|
||||
?>
|
||||
|
||||
<form action="" method="POST">
|
||||
<div>
|
||||
<?php $Strings->get("password expired"); ?>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="oldpassword"><?php $Strings->build("Current password for {user}", ["user" => htmlentities($username)]); ?></label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-lock"></i></span>
|
||||
</div>
|
||||
<input type="password" class="form-control" id="oldpassword" name="oldpassword" placeholder="" required autofocus>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="newpassword"><?php $Strings->get("New password"); ?></label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-lock"></i></span>
|
||||
</div>
|
||||
<input type="password" class="form-control" id="newpassword" name="newpassword" placeholder="" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="newpassword2"><?php $Strings->get("New password (again)"); ?></label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-lock"></i></span>
|
||||
</div>
|
||||
<input type="password" class="form-control" id="newpassword2" name="newpassword2" placeholder="" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex">
|
||||
<button type="submit" class="btn btn-primary ml-auto">
|
||||
<i class="fas fa-chevron-right"></i> <?php $Strings->get("continue"); ?>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
@ -1,15 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<script src="../static/js/fontawesome-all.min.js"></script>
|
@ -1,59 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
header("Link: <../static/fonts/Roboto.css>; rel=preload; as=style", false);
|
||||
header("Link: <../static/css/bootstrap.min.css>; rel=preload; as=style", false);
|
||||
header("Link: <../static/css/login.css>; rel=preload; as=style", false);
|
||||
header("Link: <../static/css/svg-with-js.min.css>; rel=preload; as=style", false);
|
||||
header("Link: <../static/js/fontawesome-all.min.js>; rel=preload; as=script", false);
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title><?php echo $SETTINGS['site_title']; ?></title>
|
||||
|
||||
<link rel="icon" href="../static/img/logo.svg">
|
||||
|
||||
<link href="../static/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../static/css/login.css" rel="stylesheet">
|
||||
<link href="../static/css/svg-with-js.min.css" rel="stylesheet">
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="row justify-content-center">
|
||||
<?php
|
||||
if (!empty($APPICON)) {
|
||||
?>
|
||||
<div class="col-12 text-center">
|
||||
<img class="banner-image" src="<?php echo $APPICON; ?>" />
|
||||
</div>
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
<div class="col-12">
|
||||
<div class="blank-image"></div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<div class="col-12 text-center">
|
||||
<h1 class="display-5 mb-4"><?php $Strings->build("Login to {app}", ["app" => htmlentities($APPNAME)]); ?></h1>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-sm-8 col-lg-6">
|
||||
<div class="card mt-4">
|
||||
<div class="card-body">
|
||||
<?php
|
||||
if (!empty($error)) {
|
||||
?>
|
||||
<div class="text-danger">
|
||||
<?php echo $error; ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
@ -1,32 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$_SESSION['check'] = "password";
|
||||
$username = (new User($_SESSION['login_uid']))->getUsername();
|
||||
?>
|
||||
|
||||
<form action="" method="POST">
|
||||
<div class="form-group">
|
||||
<label for="password"><?php $Strings->build("Password for {user}", ["user" => htmlentities($username)]); ?></label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-lock"></i></span>
|
||||
</div>
|
||||
<input type="password" class="form-control" id="password" name="password" aria-describedby="passwordHelp" placeholder="" required autofocus>
|
||||
</div>
|
||||
<small id="passwordHelp" class="form-text text-muted">Enter your password.</small>
|
||||
</div>
|
||||
|
||||
<div class="d-flex">
|
||||
<a href="./?code=<?php echo htmlentities($_GET['code']); ?>&redirect=<?php echo htmlentities($_GET['redirect']); ?>&reset=1" class="btn btn-link mr-2">
|
||||
<i class="fas fa-chevron-left"></i> <?php $Strings->get("Back"); ?>
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary ml-auto">
|
||||
<i class="fas fa-chevron-right"></i> <?php $Strings->get("continue"); ?>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
@ -1,31 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$_SESSION['check'] = "totp";
|
||||
?>
|
||||
|
||||
<form action="" method="POST">
|
||||
<div class="form-group">
|
||||
<label for="totp"><?php $Strings->get("Two-factor code"); ?></label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-mobile-alt"></i></span>
|
||||
</div>
|
||||
<input type="text" class="form-control" id="totp" name="totp" aria-describedby="totpHelp" placeholder="" required autofocus>
|
||||
</div>
|
||||
<small id="passwordHelp" class="form-text text-muted">Enter the two-factor code from your mobile device.</small>
|
||||
</div>
|
||||
|
||||
<div class="d-flex">
|
||||
<a href="./?code=<?php echo htmlentities($_GET['code']); ?>&redirect=<?php echo htmlentities($_GET['redirect']); ?>&reset=1" class="btn btn-link mr-2">
|
||||
<i class="fas fa-chevron-left"></i> <?php $Strings->get("Back"); ?>
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary ml-auto">
|
||||
<i class="fas fa-chevron-right"></i> <?php $Strings->get("continue"); ?>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
@ -1,37 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$_SESSION['check'] = "username";
|
||||
?>
|
||||
|
||||
<form action="" method="POST">
|
||||
<div class="form-group">
|
||||
<label for="username"><?php $Strings->get("username"); ?></label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-user"></i></span>
|
||||
</div>
|
||||
<input type="text" class="form-control" id="username" name="username" aria-describedby="usernameHelp" placeholder="" required autofocus>
|
||||
</div>
|
||||
<small id="usernameHelp" class="form-text text-muted">Enter your username.</small>
|
||||
</div>
|
||||
|
||||
<div class="d-flex">
|
||||
<div class="ml-auto">
|
||||
<?php
|
||||
if ($SETTINGS['signups_enabled'] === true) {
|
||||
?>
|
||||
<a href="../signup/?code=<?php echo urlencode($_GET["code"]); ?>&redirect=<?php echo urlencode($_GET["redirect"]); ?>" class="btn btn-link mr-2"><?php $Strings->get("Create Account"); ?></a>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-chevron-right"></i> <?php $Strings->get("continue"); ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
244
mobile/index.php
244
mobile/index.php
@ -1,244 +0,0 @@
|
||||
<?php
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/*
|
||||
* Mobile app API
|
||||
*/
|
||||
|
||||
require __DIR__ . "/../required.php";
|
||||
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
|
||||
// Allow ping check without authentication
|
||||
if ($VARS['action'] == "ping") {
|
||||
exit(json_encode(["status" => "OK"]));
|
||||
}
|
||||
|
||||
if ($SETTINGS['mobile_enabled'] !== TRUE) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("mobile login disabled", false)]));
|
||||
}
|
||||
|
||||
// Make sure we have a username and access key
|
||||
if (empty($VARS['username']) || empty($VARS['key'])) {
|
||||
http_response_code(401);
|
||||
die(json_encode(["status" => "ERROR", "msg" => "Missing username and/or access key."]));
|
||||
}
|
||||
|
||||
$username = strtolower($VARS['username']);
|
||||
$key = strtoupper($VARS['key']);
|
||||
|
||||
// Make sure the username and key are actually legit
|
||||
// Don't check key if we're trying to generate one
|
||||
if ($VARS['action'] == "generatesynccode") {
|
||||
if (!User::byUsername($username)->exists()) {
|
||||
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
|
||||
die(json_encode(["status" => "ERROR", "msg" => "Invalid username and/or access key."]));
|
||||
}
|
||||
} else {
|
||||
$user_key_valid = $database->has('mobile_codes', ['[>]accounts' => ['uid' => 'uid']], ["AND" => ['mobile_codes.code' => $key, 'accounts.username' => $username]]);
|
||||
if ($user_key_valid !== TRUE) {
|
||||
engageRateLimit();
|
||||
Log::insert(LogType::MOBILE_BAD_KEY, null, "Username: " . $username . ", Key: " . $key);
|
||||
die(json_encode(["status" => "ERROR", "msg" => "Invalid username and/or access key."]));
|
||||
}
|
||||
}
|
||||
|
||||
// Obscure key
|
||||
if (strlen($key) > 7) {
|
||||
for ($i = 3; $i < strlen($key) - 3; $i++) {
|
||||
$key[$i] = "*";
|
||||
}
|
||||
}
|
||||
|
||||
// Process the action
|
||||
switch ($VARS['action']) {
|
||||
case "check_key":
|
||||
// Check if the username/key combo is valid.
|
||||
// If we get this far, it is, so return success.
|
||||
exit(json_encode(["status" => "OK"]));
|
||||
case "check_password":
|
||||
// Check if the user-supplied password is valid.
|
||||
engageRateLimit();
|
||||
$user = User::byUsername($username);
|
||||
if ($user->getStatus()->get() != AccountStatus::NORMAL) {
|
||||
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login failed try on web", false)]));
|
||||
}
|
||||
if ($user->checkPassword($VARS['password'])) {
|
||||
Log::insert(LogType::MOBILE_LOGIN_OK, $user->getUID(), "Key: " . $key);
|
||||
exit(json_encode(["status" => "OK", "uid" => $user->getUID()]));
|
||||
} else {
|
||||
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
|
||||
}
|
||||
case "user_info":
|
||||
engageRateLimit();
|
||||
$user = User::byUsername($username);
|
||||
if ($user->getStatus()->get() != AccountStatus::NORMAL) {
|
||||
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login failed try on web", false)]));
|
||||
}
|
||||
if ($user->checkPassword($VARS['password'])) {
|
||||
$userinfo = ["uid" => $user->getUID(), "username" => $user->getUsername(), "realname" => $user->getName(), "email" => $user->getEmail()];
|
||||
Log::insert(LogType::MOBILE_LOGIN_OK, $user->getUID(), "Key: " . $key);
|
||||
exit(json_encode(["status" => "OK", "info" => $userinfo]));
|
||||
} else {
|
||||
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
|
||||
}
|
||||
case "start_session":
|
||||
// Do a web login.
|
||||
engageRateLimit();
|
||||
$user = User::byUsername($username);
|
||||
if ($user->exists()) {
|
||||
if ($user->getStatus()->get() == AccountStatus::NORMAL) {
|
||||
if ($user->checkPassword($VARS['password'])) {
|
||||
Session::start($user);
|
||||
$_SESSION['mobile'] = true;
|
||||
exit(json_encode(["status" => "OK"]));
|
||||
}
|
||||
}
|
||||
}
|
||||
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
|
||||
case "listapps":
|
||||
$apps = $SETTINGS['apps'];
|
||||
// Format paths as absolute URLs
|
||||
foreach ($apps as $k => $v) {
|
||||
if (strpos($apps[$k]['url'], "http") === FALSE) {
|
||||
$apps[$k]['url'] = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . ($_SERVER['SERVER_PORT'] != 80 || $_SERVER['SERVER_PORT'] != 443 ? ":" . $_SERVER['SERVER_PORT'] : "") . $apps[$k]['url'];
|
||||
}
|
||||
}
|
||||
exit(json_encode(["status" => "OK", "apps" => $apps]));
|
||||
case "gencode":
|
||||
engageRateLimit();
|
||||
$user = User::byUsername($username);
|
||||
$code = "";
|
||||
do {
|
||||
$code = random_int(100000, 999999);
|
||||
} while ($database->has("onetimekeys", ["key" => $code]));
|
||||
|
||||
$database->insert("onetimekeys", ["key" => $code, "uid" => $user->getUID(), "expires" => date("Y-m-d H:i:s", strtotime("+1 minute"))]);
|
||||
|
||||
$database->delete("onetimekeys", ["expires[<]" => date("Y-m-d H:i:s")]); // cleanup
|
||||
exit(json_encode(["status" => "OK", "code" => $code]));
|
||||
case "checknotifications":
|
||||
if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
} else {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
try {
|
||||
$notifications = Notifications::get($user, false);
|
||||
exit(json_encode(["status" => "OK", "notifications" => $notifications]));
|
||||
} catch (Exception $ex) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()]));
|
||||
}
|
||||
break;
|
||||
case "readnotification":
|
||||
if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
} else {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
if (empty($VARS['id'])) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("invalid parameters", false)]));
|
||||
}
|
||||
try {
|
||||
Notifications::read($user, $VARS['id']);
|
||||
exit(json_encode(["status" => "OK"]));
|
||||
} catch (Exception $ex) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()]));
|
||||
}
|
||||
break;
|
||||
case "addnotification":
|
||||
if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
} else {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
|
||||
try {
|
||||
$timestamp = "";
|
||||
if (!empty($VARS['timestamp'])) {
|
||||
$timestamp = date("Y-m-d H:i:s", strtotime($VARS['timestamp']));
|
||||
}
|
||||
$url = "";
|
||||
if (!empty($VARS['url'])) {
|
||||
$url = $VARS['url'];
|
||||
}
|
||||
$nid = Notifications::add($user, $VARS['title'], $VARS['content'], $timestamp, $url, isset($VARS['sensitive']));
|
||||
|
||||
exit(json_encode(["status" => "OK", "id" => $nid]));
|
||||
} catch (Exception $ex) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()]));
|
||||
}
|
||||
break;
|
||||
case "deletenotification":
|
||||
if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
} else {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
|
||||
if (empty($VARS['id'])) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("invalid parameters", false)]));
|
||||
}
|
||||
try {
|
||||
Notifications::delete($user, $VARS['id']);
|
||||
exit(json_encode(["status" => "OK"]));
|
||||
} catch (Exception $ex) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()]));
|
||||
}
|
||||
break;
|
||||
case "hasotp":
|
||||
if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
} else {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
|
||||
exit(json_encode(["status" => "OK", "otp" => $user->has2fa()]));
|
||||
break;
|
||||
case "generatesynccode":
|
||||
$user = User::byUsername($username);
|
||||
if ($user->has2fa()) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("2-factor is enabled, you need to use the QR code or manual setup for security reasons", false)]));
|
||||
}
|
||||
if ($user->getStatus()->get() != AccountStatus::NORMAL) {
|
||||
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login failed try on web", false)]));
|
||||
}
|
||||
if ($user->checkPassword($VARS['password'])) {
|
||||
Log::insert(LogType::MOBILE_LOGIN_OK, $user->getUID(), "Key: " . $key);
|
||||
$code = strtoupper(substr(md5(mt_rand() . uniqid("", true)), 0, 20));
|
||||
$desc = htmlspecialchars($VARS['desc']);
|
||||
$database->insert('mobile_codes', ['uid' => $user->getUID(), 'code' => $code, 'description' => $desc]);
|
||||
exit(json_encode(["status" => "OK", "code" => $code]));
|
||||
} else {
|
||||
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
|
||||
}
|
||||
default:
|
||||
http_response_code(404);
|
||||
die(json_encode(["status" => "ERROR", "msg" => "The requested action is not available."]));
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
<#if licenseFirst??>
|
||||
${licenseFirst}
|
||||
</#if>
|
||||
${licensePrefix}This Source Code Form is subject to the terms of the Mozilla Public
|
||||
${licensePrefix}License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
${licensePrefix}file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
<#if licenseLast??>
|
||||
${licenseLast}
|
||||
</#if>
|
@ -1,6 +1,5 @@
|
||||
include.path=${php.global.include.path}
|
||||
php.version=PHP_70
|
||||
project.licensePath=./nbproject/mplheader.txt
|
||||
source.encoding=UTF-8
|
||||
src.dir=.
|
||||
tags.asp=false
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user