diff --git a/.github/delete-merged-branch-config.yml b/.github/delete-merged-branch-config.yml
index 3b15c997d..142ee3f0c 100644
--- a/.github/delete-merged-branch-config.yml
+++ b/.github/delete-merged-branch-config.yml
@@ -2,4 +2,4 @@ exclude:
- main
- stable
- develop
-delete_closed_pr: true
\ No newline at end of file
+delete_closed_pr: true
diff --git a/.github/workflows/bootstrap-icons.yml b/.github/workflows/bootstrap-icons.yml
new file mode 100644
index 000000000..3a629b582
--- /dev/null
+++ b/.github/workflows/bootstrap-icons.yml
@@ -0,0 +1,30 @@
+name: bootstrap-icons
+on:
+ push:
+ paths:
+ - src/bootstrap-icons.json
+ - .github/workflows/bootstrap-icons.yml
+ schedule:
+ - cron: "0 */6 * * *"
+ workflow_dispatch:
+jobs:
+ bootstrap-icons:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: update bootstrap-icons
+ run: |
+ curl -sL $(curl -sL https://api.github.com/repos/twbs/icons/releases/latest --header "authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" | jq -r .assets[].browser_download_url) -o bootstrap-icons.zip
+ unzip bootstrap-icons.zip
+ rm -rf src/public/bootstrap-icons bootstrap-icons.zip
+ mv bootstrap-icons-* bootstrap-icons
+ mkdir src/public/bootstrap-icons
+ for icon in $(jq -r .[] src/bootstrap-icons.json); do mv bootstrap-icons/"$icon".svg src/public/bootstrap-icons/"$icon".svg; done
+ - name: push changes
+ run: |
+ git add src/public/bootstrap-icons
+ git config user.name "GitHub"
+ git config user.email "noreply@github.com"
+ git diff-index --quiet HEAD || git commit -sm "bootstrap-icons"
+ git push
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 13e341676..2fb95acad 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -6,18 +6,12 @@ on:
paths:
- .github/workflows/docker.yml
- Dockerfile
- - frontend/**
- - backend/**
- - global/**
- rootfs/**
- src/**
pull_request:
paths:
- .github/workflows/docker.yml
- Dockerfile
- - frontend/**
- - backend/**
- - global/**
- rootfs/**
- src/**
workflow_dispatch:
@@ -56,9 +50,8 @@ jobs:
- name: version
run: |
version="$(cat .version)+$(git rev-parse --short HEAD)"
- sed -i "s|\"0.0.0\"|\"$version\"|g" frontend/js/i18n/messages.json
- sed -i "s|\"0.0.0\"|\"$version\"|g" frontend/package.json
- sed -i "s|\"0.0.0\"|\"$version\"|g" backend/package.json
+ # todo: embed version somewhere
+ #sed -i "s|\"0.0.0\"|\"$version\"|g" src/
- name: Build
uses: docker/build-push-action@v6
if: ${{ github.event_name != 'pull_request' }}
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 000000000..4b802c086
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,23 @@
+name: lint
+on:
+ push:
+ schedule:
+ - cron: "0 */6 * * *"
+ workflow_dispatch:
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: lint
+ run: |
+ cd src
+ cp -v config.php.example config.php
+ find . -name "*.php" -exec php -l {} \;
+ curl -sSfL https://github.com/wapmorgan/PhpDeprecationDetector/releases/download/"$(git ls-remote --tags https://github.com/wapmorgan/PhpDeprecationDetector | cut -d/ -f3 | grep -v v1 | sort -V | tail -1)"/phpdd-"$(git ls-remote --tags https://github.com/wapmorgan/PhpDeprecationDetector | cut -d/ -f3 | grep -v v1 | sort -V | tail -1)".phar -o phpdd.phar
+ chmod +x phpdd.phar
+ find . -name "*.php" -exec ./phpdd.phar -n {} \;
+ curl -sSfL https://github.com/vimeo/psalm/releases/latest/download/psalm.phar -o psalm.phar
+ chmod +x psalm.phar
+ ./psalm.phar --no-cache
diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml
new file mode 100644
index 000000000..5c4ef59c4
--- /dev/null
+++ b/.github/workflows/prettier.yml
@@ -0,0 +1,25 @@
+name: prettier
+on:
+ push:
+ schedule:
+ - cron: "0 */6 * * *"
+ workflow_dispatch:
+jobs:
+ prettier:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: prettier
+ run: |
+ yarn global add prettier @prettier/plugin-php prettier-plugin-tailwindcss
+ sed -i "s|doctype|DOCTYPE|g" /home/runner/.config/yarn/global/node_modules/prettier/plugins/html.js
+ sed -i "s|doctype|DOCTYPE|g" /home/runner/.config/yarn/global/node_modules/prettier/plugins/html.mjs
+ prettier . -w --end-of-line crlf --print-width 10000 --plugin /home/runner/.config/yarn/global/node_modules/@prettier/plugin-php/standalone.js --plugin /home/runner/.config/yarn/global/node_modules/prettier-plugin-tailwindcss/dist/index.mjs
+ - name: push
+ run: |
+ git add -A
+ git config user.name "GitHub"
+ git config user.email "noreply@github.com"
+ git diff-index --quiet HEAD || git commit -sm "prettier"
+ git push
diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml
index a0d27fc47..639bfd24a 100644
--- a/.github/workflows/shellcheck.yml
+++ b/.github/workflows/shellcheck.yml
@@ -12,6 +12,6 @@ jobs:
- name: Run Shellcheck
uses: ludeeus/action-shellcheck@master
with:
- check_together: 'yes'
+ check_together: "yes"
env:
SHELLCHECK_OPTS: --shell sh -e SC1091 -e SC2153 -e SC2154
diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml
index 381c8a157..846552c6e 100644
--- a/.github/workflows/spellcheck.yml
+++ b/.github/workflows/spellcheck.yml
@@ -15,4 +15,4 @@ jobs:
with:
check_filenames: true
check_hidden: true
- skip: .git,.gitignore,showdown.min.js,jquery.min.js,xregexp-all.js
+ skip: .git,.gitignore,./rootfs/app/nftd,./src/vendor
diff --git a/.github/workflows/tailwindcss-update.yml b/.github/workflows/tailwindcss-update.yml
new file mode 100644
index 000000000..8251d9681
--- /dev/null
+++ b/.github/workflows/tailwindcss-update.yml
@@ -0,0 +1,26 @@
+name: tailwindcss-update
+on:
+ push:
+ schedule:
+ - cron: "0 */6 * * *"
+ workflow_dispatch:
+jobs:
+ tailwindcss-update:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - run: cp -v src/tailwind.config.js tailwind.config.js
+ - name: update tailwindcss (minify)
+ uses: ZoeyVid/tailwindcss-update@main
+ with:
+ input: src/tailwind-input.css
+ output: src/public/tailwind.css
+ params: "--minify"
+ - name: push changes
+ run: |
+ git add --force src/public/tailwind.css
+ git config user.name "GitHub"
+ git config user.email "noreply@github.com"
+ git diff-index --quiet HEAD || git commit -sm "tailwindcss-update"
+ git push
diff --git a/.github/workflows/update-and-lint.yml b/.github/workflows/update-and-lint.yml
index 912da2eac..742bd1dd0 100644
--- a/.github/workflows/update-and-lint.yml
+++ b/.github/workflows/update-and-lint.yml
@@ -3,24 +3,16 @@ on:
push:
branches:
- develop
+ - php
schedule:
- cron: "0 */6 * * *"
workflow_dispatch:
jobs:
update-and-lint:
runs-on: ubuntu-latest
- if: ${{ github.ref_name == 'develop' }}
steps:
- name: Checkout
uses: actions/checkout@v4
- - uses: actions/setup-node@v4
- with:
- node-version: 21
- - name: eslint
- run: |
- cd backend
- yarn install --no-lockfile
- yarn eslint . --fix
- name: nginxbeautifier
run: |
yarn global add nginxbeautifier
diff --git a/.imgbotconfig b/.imgbotconfig
index a31c6d45e..845d67490 100644
--- a/.imgbotconfig
+++ b/.imgbotconfig
@@ -1,6 +1,6 @@
{
- "schedule": "daily",
- "aggressiveCompression": "true",
- "compressWiki": "true",
- "minKBReduced": 0
+ "schedule": "daily",
+ "aggressiveCompression": "true",
+ "compressWiki": "true",
+ "minKBReduced": 0
}
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 000000000..993cfb5be
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,5 @@
+src/public/tailwind.css
+src/composer.lock
+src/vendor
+
+rootfs/app/fancyindex
diff --git a/.version b/.version
deleted file mode 100644
index 22e3b6b01..000000000
--- a/.version
+++ /dev/null
@@ -1 +0,0 @@
-2.11.3
diff --git a/COPYING b/COPYING
new file mode 100644
index 000000000..0ad25db4b
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,661 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+.
diff --git a/Dockerfile b/Dockerfile
index 18290d1f8..cfcda7e87 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,50 +1,4 @@
# syntax=docker/dockerfile:labs
-FROM --platform="$BUILDPLATFORM" alpine:3.20.3 AS frontend
-COPY frontend /app
-COPY global/certbot-dns-plugins.json /app/certbot-dns-plugins.json
-ARG NODE_ENV=production \
- NODE_OPTIONS=--openssl-legacy-provider
-WORKDIR /app/frontend
-RUN apk upgrade --no-cache -a && \
- apk add --no-cache ca-certificates nodejs yarn git python3 py3-pip build-base file && \
- yarn global add clean-modules && \
- pip install setuptools --no-cache-dir --break-system-packages && \
- yarn --no-lockfile install && \
- yarn --no-lockfile build && \
- yarn cache clean --all && \
- clean-modules --yes && \
- find /app/dist -name "*.node" -type f -exec file {} \;
-COPY darkmode.css /app/dist/css/darkmode.css
-COPY security.txt /app/dist/.well-known/security.txt
-
-
-FROM --platform="$BUILDPLATFORM" alpine:3.20.3 AS build-backend
-SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
-COPY backend /app
-COPY global/certbot-dns-plugins.json /app/certbot-dns-plugins.json
-ARG NODE_ENV=production \
- TARGETARCH
-WORKDIR /app
-RUN apk upgrade --no-cache -a && \
- apk add --no-cache ca-certificates nodejs yarn file && \
- yarn global add clean-modules && \
- if [ "$TARGETARCH" = "amd64" ]; then \
- npm_config_arch=x64 npm_config_target_arch=x64 yarn install --no-lockfile && \
- for file in $(find /app/node_modules -name "*.node" -type f -exec file {} \; | grep -v "x86-64\|x86_64" | grep "aarch64\|arm64" | sed "s|\([^:]\):.*|\1|g"); do rm -v "$file"; done; \
- elif [ "$TARGETARCH" = "arm64" ]; then \
- npm_config_arch=arm64 npm_config_target_arch=arm64 yarn install --no-lockfile && \
- for file in $(find /app/node_modules -name "*.node" -type f -exec file {} \; | grep -v "aarch64\|arm64" | grep "x86-64\|x86_64" | sed "s|\([^:]\):.*|\1|g"); do rm -v "$file"; done; \
- fi && \
- yarn cache clean --all && \
- clean-modules --yes
-FROM alpine:3.20.3 AS strip-backend
-COPY --from=build-backend /app /app
-RUN apk upgrade --no-cache -a && \
- apk add --no-cache ca-certificates binutils file && \
- find /app/node_modules -name "*.node" -type f -exec strip -s {} \; && \
- find /app/node_modules -name "*.node" -type f -exec file {} \;
-
-
FROM --platform="$BUILDPLATFORM" alpine:3.20.3 AS crowdsec
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
ARG CSNB_VER=v1.0.8
@@ -67,58 +21,70 @@ RUN apk upgrade --no-cache -a && \
sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf
-FROM zoeyvid/nginx-quic:340-python
+FROM zoeyvid/nginx-quic:352-python
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
COPY rootfs /
-COPY --from=zoeyvid/certbot-docker:51 /usr/local /usr/local
-COPY --from=zoeyvid/curl-quic:416 /usr/local/bin/curl /usr/local/bin/curl
+COPY src /app/src
+
+COPY --from=zoeyvid/curl-quic:426 /usr/local/bin/curl /usr/local/bin/curl
+COPY --from=zoeyvid/valkey-static:31 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli
+COPY --from=zoeyvid/valkey-static:31 /usr/local/bin/valkey-server /usr/local/bin/valkey-server
ARG CRS_VER=v4.7.0
RUN apk upgrade --no-cache -a && \
apk add --no-cache ca-certificates tzdata tini \
- nodejs \
bash nano \
logrotate apache2-utils \
lua5.1-lzlib lua5.1-socket \
- coreutils grep findutils jq shadow su-exec \
- luarocks5.1 lua5.1-dev lua5.1-sec build-base git yarn && \
+ coreutils grep findutils jq shadow su-exec fcgi \
+ luarocks5.1 lua5.1-dev lua5.1-sec build-base git \
+ php83-fpm php83-openssl php83-iconv php83-ctype php83-curl php83-session php83-sqlite3 php83-pecl-redis && \
+ \
+ cp -var /etc/php83 /etc/php && \
+ sed -i "s|;\?listen\s*=.*|listen = /run/php.sock|g" /etc/php/php-fpm.d/www.conf && \
+ sed -i "s|;\?error_log\s*=.*|error_log = /proc/self/fd/2|g" /etc/php/php-fpm.conf && \
+ sed -i "s|;\?include\s*=.*|include = /etc/php/php-fpm.d/*.conf|g" /etc/php/php-fpm.conf && \
+ sed -i "s|;\?session.save_handler\s*=.*|session.save_handler = redis|g" /etc/php/php.ini && \
+ sed -i "s|;\?session.save_path\s*=.*|session.save_path = unix:///run/valkey.sock|g" /etc/php/php.ini && \
+ sed -i "s|;\?session.name\s*=.*|session.name = NPMPLUSSESSIONID|g" /etc/php/php.ini && \
+ sed -i "s|;\?session.auto_start\s*=.*|session.auto_start = 1|g" /etc/php/php.ini && \
+ sed -i "s|;\?session.use_strict_mode\s*=.*|session.use_strict_mode = 1|g" /etc/php/php.ini && \
+ sed -i "s|;\?session.cookie_secure\s*=.*|session.cookie_secure = 1|g" /etc/php/php.ini && \
+ sed -i "s|;\?session.cookie_httponly\s*=.*|session.cookie_httponly = 1|g" /etc/php/php.ini && \
+ sed -i "s|;\?session.cookie_samesite\s*=.*|session.cookie_samesite = Strict|g" /etc/php/php.ini && \
+ \
+ mv -v /app/src/config.php.example /app/src/config.php && \
+ \
curl https://raw.githubusercontent.com/acmesh-official/acme.sh/master/acme.sh | sh -s -- --install-online --home /usr/local/acme.sh --nocron && \
+ ln -s /usr/local/acme.sh/acme.sh /usr/local/bin/acme.sh && \
+ \
curl https://raw.githubusercontent.com/tomwassenberg/certbot-ocsp-fetcher/refs/heads/main/certbot-ocsp-fetcher -o /usr/local/bin/certbot-ocsp-fetcher.sh && \
chmod +x /usr/local/bin/certbot-ocsp-fetcher.sh && \
+ \
git clone https://github.com/coreruleset/coreruleset --branch "$CRS_VER" /tmp/coreruleset && \
mkdir -v /usr/local/nginx/conf/conf.d/include/coreruleset && \
mv -v /tmp/coreruleset/crs-setup.conf.example /usr/local/nginx/conf/conf.d/include/coreruleset/crs-setup.conf.example && \
mv -v /tmp/coreruleset/plugins /usr/local/nginx/conf/conf.d/include/coreruleset/plugins && \
mv -v /tmp/coreruleset/rules /usr/local/nginx/conf/conf.d/include/coreruleset/rules && \
rm -r /tmp/* && \
+ \
luarocks-5.1 install lua-cjson && \
luarocks-5.1 install lua-resty-http && \
luarocks-5.1 install lua-resty-string && \
luarocks-5.1 install lua-resty-openssl && \
- yarn global add nginxbeautifier && \
- apk del --no-cache luarocks5.1 lua5.1-dev lua5.1-sec build-base git yarn
+ \
+ apk del --no-cache luarocks5.1 lua5.1-dev lua5.1-sec build-base git
-COPY --from=strip-backend /app /app
-COPY --from=frontend /app/dist /html/frontend
-COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/lib/plugins /usr/local/nginx/lib/lua/plugins
-COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/lib/crowdsec.lua /usr/local/nginx/lib/lua/crowdsec.lua
-COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/templates/ban.html /usr/local/nginx/conf/conf.d/include/ban.html
-COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/templates/captcha.html /usr/local/nginx/conf/conf.d/include/captcha.html
-COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf /usr/local/nginx/conf/conf.d/include/crowdsec.conf
-COPY --from=crowdsec /src/crowdsec-nginx-bouncer/nginx/crowdsec_nginx.conf /usr/local/nginx/conf/conf.d/include/crowdsec_nginx.conf
+COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/lib/plugins /usr/local/nginx/lib/lua/plugins
+COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/lib/crowdsec.lua /usr/local/nginx/lib/lua/crowdsec.lua
+COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/templates/ban.html /usr/local/nginx/conf/conf.d/include/ban.html
+COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/templates/captcha.html /usr/local/nginx/conf/conf.d/include/captcha.html
+COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf /usr/local/nginx/conf/conf.d/include/crowdsec.conf
+COPY --from=crowdsec /src/crowdsec-nginx-bouncer/nginx/crowdsec_nginx.conf /usr/local/nginx/conf/conf.d/include/crowdsec_nginx.conf
-RUN ln -s /usr/local/acme.sh/acme.sh /usr/local/bin/acme.sh && \
- ln -s /app/password-reset.js /usr/local/bin/password-reset.js && \
- ln -s /app/sqlite-vaccum.js /usr/local/bin/sqlite-vaccum.js && \
- ln -s /app/index.js /usr/local/bin/index.js
-
-LABEL com.centurylinklabs.watchtower.monitor-only="true"
-ENV NODE_ENV=production \
- NODE_CONFIG_DIR=/data/etc/npm \
- DB_SQLITE_FILE=/data/etc/npm/database.sqlite
+# todo move to ui
ENV PUID=0 \
PGID=0 \
- NIBEP=48693 \
GOAIWSP=48683 \
NPM_PORT=81 \
GOA_PORT=91 \
@@ -153,7 +119,7 @@ ENV PUID=0 \
PHP82=false \
PHP83=false
-WORKDIR /app
+LABEL com.centurylinklabs.watchtower.monitor-only="true"
ENTRYPOINT ["tini", "--", "entrypoint.sh"]
HEALTHCHECK CMD healthcheck.sh
EXPOSE 80/tcp
diff --git a/README.md b/README.md
index 4e4f02cd0..a985b7aa8 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,19 @@
# NPMplus
+
+
This project comes as a pre-built docker image that enables you to easily forward to your websites
running at home or otherwise, including free TLS, without having to know too much about Nginx or Certbot.
- [Quick Setup](#quick-setup)
+ --->
+
**Note: Reloading the NPMplus UI can cause a 502 error. See https://github.com/ZoeyVid/NPMplus/issues/241.**
**Note: NO armv7, route53 and aws cloudfront ip ranges support.**
**Note: add `net.ipv4.ip_unprivileged_port_start=0` at the end of `/etc/sysctl.conf` to support PUID/PGID in network mode host.**
@@ -22,8 +25,8 @@ running at home or otherwise, including free TLS, without having to know too muc
**Note: access.log/stream.log, logrotate and goaccess are NOT enabled by default bceuase of GDPR, you can enable them in the compose.yaml.**
**Note: if you remove a cert, which is still used by a host, NPM/NPMplus will crash.**
-
## Project Goal
+
I created this project to fill a personal need to provide users with an easy way to accomplish reverse
proxying hosts with TLS termination and it had to be so easy that a monkey could do it. This goal hasn't changed.
While there might be advanced options they are optional and the project should be as simple as possible
@@ -34,7 +37,6 @@ so that the barrier for entry here is low.
--->
-
## Features
- Beautiful and Secure Admin Interface based on [Tabler](https://tabler.github.io)
@@ -44,7 +46,6 @@ so that the barrier for entry here is low.
- Advanced Nginx configuration available for super users
- User management, permissions and audit log
-
# List of new features
- Supports HTTP/3 (QUIC) protocol.
@@ -54,9 +55,9 @@ so that the barrier for entry here is low.
- If the core ruleset blocks valid requests, please check the `/opt/npm/etc/modsecurity/crs-setup.conf` file.
- Try to whitelist the Content-Type you are sending (for example, `application/activity+json` for Mastodon and `application/dns-message` for DoH).
- Try to whitelist the HTTP request method you are using (for example, `PUT` is blocked by default, which also affects NPM).
-
+ --->
- Darkmode button in the footer for comfortable viewing (CSS done by [@theraw](https://github.com/theraw))
- Fixes proxy to https origin when the origin only accepts TLSv1.3
- Only enables TLSv1.2 and TLSv1.3 protocols
@@ -90,6 +91,7 @@ so that the barrier for entry here is low.
- If you want to redirect all HTTP traffic to HTTPS, you can use the `compose.override.yaml` file.
## migration
+
- **NOTE: migrating back to the original is not possible**, so make first a **backup** before migration, so you can use the backup to switch back
- since many buttons changed, please edit every host you have and click save. (Please also resave it, if all buttons/values are fine, to update the host config to fully fit the NPMplus template)
- please delete all dnspod certs and recreate them OR you manually change the credentialsfile (see [here](https://github.com/ZoeyVid/npmplus/blob/develop/global/certbot-dns-plugins.js) for the template)
@@ -98,8 +100,10 @@ so that the barrier for entry here is low.
- please report all migration issues you have
# Crowdsec
+
1. Install crowdsec using this compose file: https://github.com/ZoeyVid/NPMplus/blob/develop/compose.crowdsec.yaml and enable LOGROTATE
2. open `/opt/crowdsec/conf/acquis.d/npmplus.yaml` and fill it with:
+
```yaml
filenames:
- /opt/npm/nginx/access.log
@@ -108,13 +112,13 @@ labels:
---
source: docker
container_name:
- - npmplus
+ - npmplus
labels:
type: npmplus
---
source: docker
container_name:
- - npmplus
+ - npmplus
labels:
type: modsecurity
---
@@ -125,6 +129,7 @@ source: appsec
labels:
type: appsec
```
+
3. make sure to use `network_mode: host` in your compose file
4. run `docker exec crowdsec cscli bouncers add npmplus -o raw` and save the output
5. open `/opt/npm/etc/crowdsec/crowdsec.conf`
@@ -135,18 +140,21 @@ labels:
10. redeploy the `compose.yaml`
# coreruleset plugins
+
1. Download the plugin (all files inside the `plugins` folder of the git repo), most time: `-before.conf`, `-config.conf` and `-after.conf` and sometimes `.data` and/or `.lua` or somilar files
2. put them into the `/opt/npm/etc/modsecurity/crs-plugins` folder
3. maybe open the `/opt/npm/etc/modsecurity/crs-plugins/-config.conf` and configure the plugin
# Use as webserver
+
1. Create a new Proxy Host
2. Set `Scheme` to `https`, `Forward Hostname / IP` to `0.0.0.0`, `Forward Port` to `1` and enable `Websockets Support` (you can also use other values, since these get fully ignored)
3. Maybe set an Access List
4. Make your TLS Settings
-5.
-a) Custom Nginx Configuration (advanced tab), which looks the following for file server:
+5. a) Custom Nginx Configuration (advanced tab), which looks the following for file server:
+
- Note: the slash at the end of the file path is important
+
```
location / {
include conf.d/include/always.conf;
@@ -154,11 +162,14 @@ location / {
fancyindex off; # alternative to nginxs "index" option (looks better and has more options)
}
```
+
b) Custom Nginx Configuration (advanced tab), which looks the following for file server and **php**:
+
- Note: the slash at the end of the file path is important
- Note: first enable `PHP82` and/or `PHP83` inside your compose file
- Note: you can replace `fastcgi_pass php82;` with `fastcgi_pass php83;`
- Note: to add more php extension using envs you can set in the compose file
+
```
location / {
include conf.d/include/always.conf;
@@ -176,51 +187,64 @@ location / {
```
# custom acme server
+
1. Open this file: `nano` `/opt/npm/ssl/certbot/config.ini`
2. uncomment the server line and change it to your acme server
3. maybe set eab keys
4. create your cert using the npm web ui
# Quick Setup
+
1. Install Docker and Docker Compose (or portainer)
+
- [Docker Install documentation](https://docs.docker.com/engine)
- [Docker Compose Install documentation](https://docs.docker.com/compose/install/linux)
+
2. Create a compose.yaml file similar to [this](https://github.com/ZoeyVid/NPMplus/blob/develop/compose.yaml) (or use it as a portainer stack):
3. Bring up your stack by running (or deploy your portainer stack)
+
```bash
docker compose up -d
```
+
4. Log in to the Admin UI
-When your docker container is running, connect to it on port `81` for the admin interface.
-Sometimes this can take a little bit because of the entropy of keys.
-You may need to open port 81 in your firewall.
-You may need to use another IP-Address.
-[https://127.0.0.1:81](https://127.0.0.1:81)
-Default Admin User:
+ When your docker container is running, connect to it on port `81` for the admin interface.
+ Sometimes this can take a little bit because of the entropy of keys.
+ You may need to open port 81 in your firewall.
+ You may need to use another IP-Address.
+ [https://127.0.0.1:81](https://127.0.0.1:81)
+ Default Admin User:
+
```
Email: admin@example.org
Password: iArhP1j7p1P6TA92FA2FMbbUGYqwcYzxC4AVEe12Wbi94FY9gNN62aKyF1shrvG4NycjjX9KfmDQiwkLZH1ZDR9xMjiG2QmoHXi
```
+
Immediately after logging in with this default user you will be asked to modify your details and change your password.
### prerun scripts (EXPERT option) - if you don't know what this is, ignore it
+
run order: entrypoint.sh (prerun scripts) => start.sh => launch.sh
if you need to run scripts before NPMplus launches put them under: `/opt/npm/etc/prerun/*.sh` (please add `#!/bin/sh` / `#!/bin/bash` to the top of the script)
you need to create this folder yourself - **NOTE:** I won't help you creating those patches/scripts if you need them you also need to know how to create them
## Contributing
+
All are welcome to create pull requests for this project, against the `develop` branch.
CI is used in this project. All PR's must pass before being considered. After passing,
docker builds for PR's are available on ghcr for manual verifications.
## Contributors/Sponsor upstream NPM
+
Special thanks to [all of our contributors](https://github.com/NginxProxyManager/nginx-proxy-manager/graphs/contributors).
If you want to sponsor them, please see [here](https://github.com/NginxProxyManager/nginx-proxy-manager/blob/master/README.md).
# Please report Bugs first to this fork before reporting them to the upstream Repository
+
## Getting Support
+
1. [Found a bug?](https://github.com/ZoeyVid/NPMplus/issues)
2. [Discussions](https://github.com/ZoeyVid/NPMplus/discussions)
+ --->
diff --git a/backend/.gitignore b/backend/.gitignore
deleted file mode 100644
index 149080b91..000000000
--- a/backend/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-config/development.json
-data/*
-yarn-error.log
-tmp
-certbot.log
-node_modules
-core.*
-
diff --git a/backend/.prettierrc b/backend/.prettierrc
deleted file mode 100644
index 6a534db77..000000000
--- a/backend/.prettierrc
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "semi": true,
- "useTabs": true,
- "printWidth": 1000,
- "singleQuote": true,
- "bracketSameLine": true
-}
diff --git a/backend/app.js b/backend/app.js
deleted file mode 100644
index e2c1d5b56..000000000
--- a/backend/app.js
+++ /dev/null
@@ -1,87 +0,0 @@
-const express = require('express');
-const bodyParser = require('body-parser');
-const fileUpload = require('express-fileupload');
-const compression = require('compression');
-const config = require('./lib/config');
-const log = require('./logger').express;
-
-/**
- * App
- */
-const app = express();
-app.use(fileUpload());
-app.use(bodyParser.json());
-app.use(bodyParser.urlencoded({ extended: true }));
-
-// Gzip
-app.use(compression());
-
-/**
- * General Logging, BEFORE routes
- */
-
-app.disable('x-powered-by');
-app.enable('trust proxy', ['loopback', 'linklocal', 'uniquelocal']);
-app.enable('strict routing');
-
-// pretty print JSON when not live
-if (config.debug()) {
- app.set('json spaces', 2);
-}
-
-// CORS for everything
-app.use(require('./lib/express/cors'));
-
-// General security/cache related headers + server header
-app.use(function (req, res, next) {
- let x_frame_options = 'DENY';
-
- if (typeof process.env.X_FRAME_OPTIONS !== 'undefined' && process.env.X_FRAME_OPTIONS) {
- x_frame_options = process.env.X_FRAME_OPTIONS;
- }
-
- res.set({
- 'X-XSS-Protection': '1; mode=block',
- 'X-Content-Type-Options': 'nosniff',
- 'X-Frame-Options': x_frame_options,
- 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
- Pragma: 'no-cache',
- Expires: 0,
- });
- next();
-});
-
-app.use(require('./lib/express/jwt')());
-app.use('/', require('./routes/api/main'));
-
-// production error handler
-// no stacktraces leaked to user
-// eslint-disable-next-line
-app.use(function (err, req, res, next) {
- const payload = {
- error: {
- code: err.status,
- message: err.public ? err.message : 'Internal Error',
- },
- };
-
- if (config.debug() || (req.baseUrl + req.path).includes('nginx/certificates')) {
- payload.debug = {
- stack: typeof err.stack !== 'undefined' && err.stack ? err.stack.split('\n') : null,
- previous: err.previous,
- };
- }
-
- // Not every error is worth logging - but this is good for now until it gets annoying.
- if (typeof err.stack !== 'undefined' && err.stack) {
- if (config.debug()) {
- log.debug(err.stack);
- } else if (typeof err.public === 'undefined' || !err.public) {
- log.warn(err.message);
- }
- }
-
- res.status(err.status || 500).send(payload);
-});
-
-module.exports = app;
diff --git a/backend/db.js b/backend/db.js
deleted file mode 100644
index c3fec0823..000000000
--- a/backend/db.js
+++ /dev/null
@@ -1,28 +0,0 @@
-const config = require('./lib/config');
-
-if (!config.has('database')) {
- throw new Error('Database config does not exist! Please read the instructions: https://github.com/ZoeyVid/NPMplus');
-}
-
-function generateDbConfig() {
- const cfg = config.get('database');
- if (cfg.engine === 'knex-native') {
- return cfg.knex;
- }
- return {
- client: cfg.engine,
- connection: {
- host: cfg.host,
- user: cfg.user,
- password: cfg.password,
- database: cfg.name,
- port: cfg.port,
- ssl: cfg.tls,
- },
- migrations: {
- tableName: 'migrations',
- },
- };
-}
-
-module.exports = require('knex')(generateDbConfig());
diff --git a/backend/doc/api.swagger.json b/backend/doc/api.swagger.json
deleted file mode 100644
index c48084994..000000000
--- a/backend/doc/api.swagger.json
+++ /dev/null
@@ -1,1456 +0,0 @@
-{
- "openapi": "3.0.0",
- "info": {
- "title": "NPMplus API",
- "version": "2.x.x"
- },
- "servers": [
- {
- "url": "https://127.0.0.1:81/api"
- }
- ],
- "paths": {
- "/": {
- "get": {
- "operationId": "health",
- "summary": "Returns the API health status",
- "responses": {
- "200": {
- "description": "200 response",
- "content": {
- "application/json": {
- "examples": {
- "default": {
- "value": {
- "status": "OK",
- "version": {
- "major": 2,
- "minor": 1,
- "revision": 0
- }
- }
- }
- },
- "schema": {
- "$ref": "#/components/schemas/HealthObject"
- }
- }
- }
- }
- }
- }
- },
- "/nginx/proxy-hosts": {
- "get": {
- "operationId": "getProxyHosts",
- "summary": "Get all proxy hosts",
- "tags": ["Proxy Hosts"],
- "security": [
- {
- "BearerAuth": ["users"]
- }
- ],
- "parameters": [
- {
- "in": "query",
- "name": "expand",
- "description": "Expansions",
- "schema": {
- "type": "string",
- "enum": ["access_list", "owner", "certificate"]
- }
- }
- ],
- "responses": {
- "200": {
- "description": "200 response",
- "content": {
- "application/json": {
- "examples": {
- "default": {
- "value": [
- {
- "id": 1,
- "created_on": "2023-03-30T01:12:23.000Z",
- "modified_on": "2023-03-30T02:15:40.000Z",
- "owner_user_id": 1,
- "domain_names": ["aasdasdad"],
- "forward_host": "asdasd",
- "forward_port": 80,
- "access_list_id": 0,
- "certificate_id": 0,
- "ssl_forced": 0,
- "caching_enabled": 0,
- "block_exploits": 0,
- "advanced_config": "sdfsdfsdf",
- "meta": {
- "letsencrypt_agree": false,
- "dns_challenge": false,
- "nginx_online": false,
- "nginx_err": "Command failed: nginx -t -g \"error_log off;\"\nnginx: [emerg] unknown directive \"sdfsdfsdf\" in /data/nginx/proxy_host/1.conf:37\nnginx: configuration file /urs/local/nginx/conf/nginx.conf test failed\n"
- },
- "allow_websocket_upgrade": 0,
- "http2_support": 0,
- "forward_scheme": "http",
- "enabled": 1,
- "locations": [],
- "hsts_enabled": 0,
- "hsts_subdomains": 0,
- "owner": {
- "id": 1,
- "created_on": "2023-03-30T01:11:50.000Z",
- "modified_on": "2023-03-30T01:11:50.000Z",
- "is_deleted": 0,
- "is_disabled": 0,
- "email": "admin@example.org",
- "name": "Administrator",
- "nickname": "Admin",
- "avatar": "",
- "roles": ["admin"]
- },
- "access_list": null,
- "certificate": null
- },
- {
- "id": 2,
- "created_on": "2023-03-30T02:11:49.000Z",
- "modified_on": "2023-03-30T02:11:49.000Z",
- "owner_user_id": 1,
- "domain_names": ["test.example.com"],
- "forward_host": "1.1.1.1",
- "forward_port": 80,
- "access_list_id": 0,
- "certificate_id": 0,
- "ssl_forced": 0,
- "caching_enabled": 0,
- "block_exploits": 0,
- "advanced_config": "",
- "meta": {
- "letsencrypt_agree": false,
- "dns_challenge": false,
- "nginx_online": true,
- "nginx_err": null
- },
- "allow_websocket_upgrade": 0,
- "http2_support": 0,
- "forward_scheme": "http",
- "enabled": 1,
- "locations": [],
- "hsts_enabled": 0,
- "hsts_subdomains": 0,
- "owner": {
- "id": 1,
- "created_on": "2023-03-30T01:11:50.000Z",
- "modified_on": "2023-03-30T01:11:50.000Z",
- "is_deleted": 0,
- "is_disabled": 0,
- "email": "admin@example.org",
- "name": "Administrator",
- "nickname": "Admin",
- "avatar": "",
- "roles": ["admin"]
- },
- "access_list": null,
- "certificate": null
- }
- ]
- }
- },
- "schema": {
- "$ref": "#/components/schemas/ProxyHostsList"
- }
- }
- }
- }
- }
- },
- "post": {
- "operationId": "createProxyHost",
- "summary": "Create a Proxy Host",
- "tags": ["Proxy Hosts"],
- "security": [
- {
- "BearerAuth": ["users"]
- }
- ],
- "parameters": [
- {
- "in": "body",
- "name": "proxyhost",
- "description": "Proxy Host Payload",
- "required": true,
- "schema": {
- "$ref": "#/components/schemas/ProxyHostObject"
- }
- }
- ],
- "responses": {
- "201": {
- "description": "201 response",
- "content": {
- "application/json": {
- "examples": {
- "default": {
- "value": {
- "id": 3,
- "created_on": "2023-03-30T02:31:27.000Z",
- "modified_on": "2023-03-30T02:31:27.000Z",
- "owner_user_id": 1,
- "domain_names": ["test2.example.com"],
- "forward_host": "1.1.1.1",
- "forward_port": 80,
- "access_list_id": 0,
- "certificate_id": 0,
- "ssl_forced": 0,
- "caching_enabled": 0,
- "block_exploits": 0,
- "advanced_config": "",
- "meta": {
- "letsencrypt_agree": false,
- "dns_challenge": false
- },
- "allow_websocket_upgrade": 0,
- "http2_support": 0,
- "forward_scheme": "http",
- "enabled": 1,
- "locations": [],
- "hsts_enabled": 0,
- "hsts_subdomains": 0,
- "certificate": null,
- "owner": {
- "id": 1,
- "created_on": "2023-03-30T01:11:50.000Z",
- "modified_on": "2023-03-30T01:11:50.000Z",
- "is_deleted": 0,
- "is_disabled": 0,
- "email": "admin@example.org",
- "name": "Administrator",
- "nickname": "Admin",
- "avatar": "",
- "roles": ["admin"]
- },
- "access_list": null,
- "use_default_location": true,
- "ipv6": true
- }
- }
- },
- "schema": {
- "$ref": "#/components/schemas/ProxyHostObject"
- }
- }
- }
- }
- }
- }
- },
- "/schema": {
- "get": {
- "operationId": "schema",
- "responses": {
- "200": {
- "description": "200 response"
- }
- },
- "summary": "Returns this swagger API schema"
- }
- },
- "/tokens": {
- "get": {
- "operationId": "refreshToken",
- "summary": "Refresh your access token",
- "tags": ["Tokens"],
- "security": [
- {
- "BearerAuth": ["tokens"]
- }
- ],
- "responses": {
- "200": {
- "description": "200 response",
- "content": {
- "application/json": {
- "examples": {
- "default": {
- "value": {
- "expires": 1566540510,
- "token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4"
- }
- }
- },
- "schema": {
- "$ref": "#/components/schemas/TokenObject"
- }
- }
- }
- }
- }
- },
- "post": {
- "operationId": "requestToken",
- "parameters": [
- {
- "description": "Credentials Payload",
- "in": "body",
- "name": "credentials",
- "required": true,
- "schema": {
- "additionalProperties": false,
- "properties": {
- "identity": {
- "minLength": 1,
- "type": "string"
- },
- "scope": {
- "minLength": 1,
- "type": "string",
- "enum": ["user"]
- },
- "secret": {
- "minLength": 1,
- "type": "string"
- }
- },
- "required": ["identity", "secret"],
- "type": "object"
- }
- }
- ],
- "responses": {
- "200": {
- "content": {
- "application/json": {
- "examples": {
- "default": {
- "value": {
- "result": {
- "expires": 1566540510,
- "token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4"
- }
- }
- }
- },
- "schema": {
- "$ref": "#/components/schemas/TokenObject"
- }
- }
- },
- "description": "200 response"
- }
- },
- "summary": "Request a new access token from credentials",
- "tags": ["Tokens"]
- }
- },
- "/settings": {
- "get": {
- "operationId": "getSettings",
- "summary": "Get all settings",
- "tags": ["Settings"],
- "security": [
- {
- "BearerAuth": ["settings"]
- }
- ],
- "responses": {
- "200": {
- "description": "200 response",
- "content": {
- "application/json": {
- "examples": {
- "default": {
- "value": [
- {
- "id": "default-site",
- "name": "Default Site",
- "description": "What to show when Nginx is hit with an unknown Host",
- "value": "congratulations",
- "meta": {}
- }
- ]
- }
- },
- "schema": {
- "$ref": "#/components/schemas/SettingsList"
- }
- }
- }
- }
- }
- }
- },
- "/settings/{settingID}": {
- "get": {
- "operationId": "getSetting",
- "summary": "Get a setting",
- "tags": ["Settings"],
- "security": [
- {
- "BearerAuth": ["settings"]
- }
- ],
- "parameters": [
- {
- "in": "path",
- "name": "settingID",
- "schema": {
- "type": "string",
- "minLength": 1
- },
- "required": true,
- "description": "Setting ID",
- "example": "default-site"
- }
- ],
- "responses": {
- "200": {
- "description": "200 response",
- "content": {
- "application/json": {
- "examples": {
- "default": {
- "value": {
- "id": "default-site",
- "name": "Default Site",
- "description": "What to show when Nginx is hit with an unknown Host",
- "value": "congratulations",
- "meta": {}
- }
- }
- },
- "schema": {
- "$ref": "#/components/schemas/SettingObject"
- }
- }
- }
- }
- }
- },
- "put": {
- "operationId": "updateSetting",
- "summary": "Update a setting",
- "tags": ["Settings"],
- "security": [
- {
- "BearerAuth": ["settings"]
- }
- ],
- "parameters": [
- {
- "in": "path",
- "name": "settingID",
- "schema": {
- "type": "string",
- "minLength": 1
- },
- "required": true,
- "description": "Setting ID",
- "example": "default-site"
- },
- {
- "in": "body",
- "name": "setting",
- "description": "Setting Payload",
- "required": true,
- "schema": {
- "$ref": "#/components/schemas/SettingObject"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "200 response",
- "content": {
- "application/json": {
- "examples": {
- "default": {
- "value": {
- "id": "default-site",
- "name": "Default Site",
- "description": "What to show when Nginx is hit with an unknown Host",
- "value": "congratulations",
- "meta": {}
- }
- }
- },
- "schema": {
- "$ref": "#/components/schemas/SettingObject"
- }
- }
- }
- }
- }
- }
- },
- "/users": {
- "get": {
- "operationId": "getUsers",
- "summary": "Get all users",
- "tags": ["Users"],
- "security": [
- {
- "BearerAuth": ["users"]
- }
- ],
- "parameters": [
- {
- "in": "query",
- "name": "expand",
- "description": "Expansions",
- "schema": {
- "type": "string",
- "enum": ["permissions"]
- }
- }
- ],
- "responses": {
- "200": {
- "description": "200 response",
- "content": {
- "application/json": {
- "examples": {
- "default": {
- "value": [
- {
- "id": 1,
- "created_on": "2020-01-30T09:36:08.000Z",
- "modified_on": "2020-01-30T09:41:04.000Z",
- "is_disabled": 0,
- "email": "jc@jc21.com",
- "name": "Jamie Curnow",
- "nickname": "James",
- "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
- "roles": ["admin"]
- }
- ]
- },
- "withPermissions": {
- "value": [
- {
- "id": 1,
- "created_on": "2020-01-30T09:36:08.000Z",
- "modified_on": "2020-01-30T09:41:04.000Z",
- "is_disabled": 0,
- "email": "jc@jc21.com",
- "name": "Jamie Curnow",
- "nickname": "James",
- "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
- "roles": ["admin"],
- "permissions": {
- "visibility": "all",
- "proxy_hosts": "manage",
- "redirection_hosts": "manage",
- "dead_hosts": "manage",
- "streams": "manage",
- "access_lists": "manage",
- "certificates": "manage"
- }
- }
- ]
- }
- },
- "schema": {
- "$ref": "#/components/schemas/UsersList"
- }
- }
- }
- }
- }
- },
- "post": {
- "operationId": "createUser",
- "summary": "Create a User",
- "tags": ["Users"],
- "security": [
- {
- "BearerAuth": ["users"]
- }
- ],
- "parameters": [
- {
- "in": "body",
- "name": "user",
- "description": "User Payload",
- "required": true,
- "schema": {
- "$ref": "#/components/schemas/UserObject"
- }
- }
- ],
- "responses": {
- "201": {
- "description": "201 response",
- "content": {
- "application/json": {
- "examples": {
- "default": {
- "value": {
- "id": 2,
- "created_on": "2020-01-30T09:36:08.000Z",
- "modified_on": "2020-01-30T09:41:04.000Z",
- "is_disabled": 0,
- "email": "jc@jc21.com",
- "name": "Jamie Curnow",
- "nickname": "James",
- "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
- "roles": ["admin"],
- "permissions": {
- "visibility": "all",
- "proxy_hosts": "manage",
- "redirection_hosts": "manage",
- "dead_hosts": "manage",
- "streams": "manage",
- "access_lists": "manage",
- "certificates": "manage"
- }
- }
- }
- },
- "schema": {
- "$ref": "#/components/schemas/UserObject"
- }
- }
- }
- }
- }
- }
- },
- "/users/{userID}": {
- "get": {
- "operationId": "getUser",
- "summary": "Get a user",
- "tags": ["Users"],
- "security": [
- {
- "BearerAuth": ["users"]
- }
- ],
- "parameters": [
- {
- "in": "path",
- "name": "userID",
- "schema": {
- "oneOf": [
- {
- "type": "string",
- "pattern": "^me$"
- },
- {
- "type": "integer",
- "minimum": 1
- }
- ]
- },
- "required": true,
- "description": "User ID or 'me' for yourself",
- "example": 1
- }
- ],
- "responses": {
- "200": {
- "description": "200 response",
- "content": {
- "application/json": {
- "examples": {
- "default": {
- "value": {
- "id": 1,
- "created_on": "2020-01-30T09:36:08.000Z",
- "modified_on": "2020-01-30T09:41:04.000Z",
- "is_disabled": 0,
- "email": "jc@jc21.com",
- "name": "Jamie Curnow",
- "nickname": "James",
- "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
- "roles": ["admin"]
- }
- }
- },
- "schema": {
- "$ref": "#/components/schemas/UserObject"
- }
- }
- }
- }
- }
- },
- "put": {
- "operationId": "updateUser",
- "summary": "Update a User",
- "tags": ["Users"],
- "security": [
- {
- "BearerAuth": ["users"]
- }
- ],
- "parameters": [
- {
- "in": "path",
- "name": "userID",
- "schema": {
- "oneOf": [
- {
- "type": "string",
- "pattern": "^me$"
- },
- {
- "type": "integer",
- "minimum": 1
- }
- ]
- },
- "required": true,
- "description": "User ID or 'me' for yourself",
- "example": 2
- },
- {
- "in": "body",
- "name": "user",
- "description": "User Payload",
- "required": true,
- "schema": {
- "$ref": "#/components/schemas/UserObject"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "200 response",
- "content": {
- "application/json": {
- "examples": {
- "default": {
- "value": {
- "id": 2,
- "created_on": "2020-01-30T09:36:08.000Z",
- "modified_on": "2020-01-30T09:41:04.000Z",
- "is_disabled": 0,
- "email": "jc@jc21.com",
- "name": "Jamie Curnow",
- "nickname": "James",
- "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
- "roles": ["admin"]
- }
- }
- },
- "schema": {
- "$ref": "#/components/schemas/UserObject"
- }
- }
- }
- }
- }
- },
- "delete": {
- "operationId": "deleteUser",
- "summary": "Delete a User",
- "tags": ["Users"],
- "security": [
- {
- "BearerAuth": ["users"]
- }
- ],
- "parameters": [
- {
- "in": "path",
- "name": "userID",
- "schema": {
- "type": "integer",
- "minimum": 1
- },
- "required": true,
- "description": "User ID",
- "example": 2
- }
- ],
- "responses": {
- "200": {
- "description": "200 response",
- "content": {
- "application/json": {
- "examples": {
- "default": {
- "value": true
- }
- },
- "schema": {
- "type": "boolean"
- }
- }
- }
- }
- }
- }
- },
- "/users/{userID}/auth": {
- "put": {
- "operationId": "updateUserAuth",
- "summary": "Update a User's Authentication",
- "tags": ["Users"],
- "security": [
- {
- "BearerAuth": ["users"]
- }
- ],
- "parameters": [
- {
- "in": "path",
- "name": "userID",
- "schema": {
- "oneOf": [
- {
- "type": "string",
- "pattern": "^me$"
- },
- {
- "type": "integer",
- "minimum": 1
- }
- ]
- },
- "required": true,
- "description": "User ID or 'me' for yourself",
- "example": 2
- },
- {
- "in": "body",
- "name": "user",
- "description": "User Payload",
- "required": true,
- "schema": {
- "$ref": "#/components/schemas/AuthObject"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "200 response",
- "content": {
- "application/json": {
- "examples": {
- "default": {
- "value": true
- }
- },
- "schema": {
- "type": "boolean"
- }
- }
- }
- }
- }
- }
- },
- "/users/{userID}/permissions": {
- "put": {
- "operationId": "updateUserPermissions",
- "summary": "Update a User's Permissions",
- "tags": ["Users"],
- "security": [
- {
- "BearerAuth": ["users"]
- }
- ],
- "parameters": [
- {
- "in": "path",
- "name": "userID",
- "schema": {
- "type": "integer",
- "minimum": 1
- },
- "required": true,
- "description": "User ID",
- "example": 2
- },
- {
- "in": "body",
- "name": "user",
- "description": "Permissions Payload",
- "required": true,
- "schema": {
- "$ref": "#/components/schemas/PermissionsObject"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "200 response",
- "content": {
- "application/json": {
- "examples": {
- "default": {
- "value": true
- }
- },
- "schema": {
- "type": "boolean"
- }
- }
- }
- }
- }
- }
- },
- "/users/{userID}/login": {
- "put": {
- "operationId": "loginAsUser",
- "summary": "Login as this user",
- "tags": ["Users"],
- "security": [
- {
- "BearerAuth": ["users"]
- }
- ],
- "parameters": [
- {
- "in": "path",
- "name": "userID",
- "schema": {
- "type": "integer",
- "minimum": 1
- },
- "required": true,
- "description": "User ID",
- "example": 2
- }
- ],
- "responses": {
- "200": {
- "description": "200 response",
- "content": {
- "application/json": {
- "examples": {
- "default": {
- "value": {
- "token": "eyJhbGciOiJSUzI1NiIsInR...16OjT8B3NLyXg",
- "expires": "2020-01-31T10:56:23.239Z",
- "user": {
- "id": 1,
- "created_on": "2020-01-30T10:43:44.000Z",
- "modified_on": "2020-01-30T10:43:44.000Z",
- "is_disabled": 0,
- "email": "jc@jc21.com",
- "name": "Jamie Curnow",
- "nickname": "James",
- "avatar": "//www.gravatar.com/avatar/3c8d73f45fd8763f827b964c76e6032a?default=mm",
- "roles": ["admin"]
- }
- }
- }
- },
- "schema": {
- "type": "object",
- "description": "Login object",
- "required": ["expires", "token", "user"],
- "additionalProperties": false,
- "properties": {
- "expires": {
- "description": "Token Expiry Unix Time",
- "example": 1566540249,
- "minimum": 1,
- "type": "number"
- },
- "token": {
- "description": "JWT Token",
- "example": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4",
- "type": "string"
- },
- "user": {
- "$ref": "#/components/schemas/UserObject"
- }
- }
- }
- }
- }
- }
- }
- }
- },
- "/reports/hosts": {
- "get": {
- "operationId": "reportsHosts",
- "summary": "Report on Host Statistics",
- "tags": ["Reports"],
- "security": [
- {
- "BearerAuth": ["reports"]
- }
- ],
- "responses": {
- "200": {
- "description": "200 response",
- "content": {
- "application/json": {
- "examples": {
- "default": {
- "value": {
- "proxy": 20,
- "redirection": 1,
- "stream": 0,
- "dead": 1
- }
- }
- },
- "schema": {
- "$ref": "#/components/schemas/HostReportObject"
- }
- }
- }
- }
- }
- }
- },
- "/audit-log": {
- "get": {
- "operationId": "getAuditLog",
- "summary": "Get Audit Log",
- "tags": ["Audit Log"],
- "security": [
- {
- "BearerAuth": ["audit-log"]
- }
- ],
- "responses": {
- "200": {
- "description": "200 response",
- "content": {
- "application/json": {
- "examples": {
- "default": {
- "value": {
- "proxy": 20,
- "redirection": 1,
- "stream": 0,
- "dead": 1
- }
- }
- },
- "schema": {
- "$ref": "#/components/schemas/HostReportObject"
- }
- }
- }
- }
- }
- }
- }
- },
- "components": {
- "securitySchemes": {
- "BearerAuth": {
- "type": "http",
- "scheme": "bearer"
- }
- },
- "schemas": {
- "HealthObject": {
- "type": "object",
- "description": "Health object",
- "additionalProperties": false,
- "required": ["status", "version"],
- "properties": {
- "status": {
- "type": "string",
- "description": "Healthy",
- "example": "OK"
- },
- "version": {
- "type": "object",
- "description": "The version object",
- "example": {
- "major": 2,
- "minor": 0,
- "revision": 0
- },
- "additionalProperties": false,
- "required": ["major", "minor", "revision"],
- "properties": {
- "major": {
- "type": "integer",
- "minimum": 0
- },
- "minor": {
- "type": "integer",
- "minimum": 0
- },
- "revision": {
- "type": "integer",
- "minimum": 0
- }
- }
- }
- }
- },
- "TokenObject": {
- "type": "object",
- "description": "Token object",
- "required": ["expires", "token"],
- "additionalProperties": false,
- "properties": {
- "expires": {
- "description": "Token Expiry Unix Time",
- "example": 1566540249,
- "minimum": 1,
- "type": "number"
- },
- "token": {
- "description": "JWT Token",
- "example": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4",
- "type": "string"
- }
- }
- },
- "ProxyHostObject": {
- "type": "object",
- "description": "Proxy Host object",
- "required": [
- "id",
- "created_on",
- "modified_on",
- "owner_user_id",
- "domain_names",
- "forward_host",
- "forward_port",
- "access_list_id",
- "certificate_id",
- "ssl_forced",
- "caching_enabled",
- "block_exploits",
- "advanced_config",
- "meta",
- "allow_websocket_upgrade",
- "http2_support",
- "forward_scheme",
- "enabled",
- "locations",
- "hsts_enabled",
- "hsts_subdomains",
- "certificate",
- "use_default_location",
- "ipv6"
- ],
- "additionalProperties": false,
- "properties": {
- "id": {
- "type": "integer",
- "description": "Proxy Host ID",
- "minimum": 1,
- "example": 1
- },
- "created_on": {
- "type": "string",
- "description": "Created Date",
- "example": "2020-01-30T09:36:08.000Z"
- },
- "modified_on": {
- "type": "string",
- "description": "Modified Date",
- "example": "2020-01-30T09:41:04.000Z"
- },
- "owner_user_id": {
- "type": "integer",
- "minimum": 1,
- "example": 1
- },
- "domain_names": {
- "type": "array",
- "minItems": 1,
- "items": {
- "type": "string",
- "minLength": 1
- }
- },
- "forward_host": {
- "type": "string",
- "minLength": 1
- },
- "forward_port": {
- "type": "integer",
- "minimum": 1
- },
- "access_list_id": {
- "type": "integer"
- },
- "certificate_id": {
- "type": "integer"
- },
- "ssl_forced": {
- "type": "integer"
- },
- "caching_enabled": {
- "type": "integer"
- },
- "block_exploits": {
- "type": "integer"
- },
- "advanced_config": {
- "type": "string"
- },
- "meta": {
- "type": "object"
- },
- "allow_websocket_upgrade": {
- "type": "integer"
- },
- "http2_support": {
- "type": "integer"
- },
- "forward_scheme": {
- "type": "string"
- },
- "enabled": {
- "type": "integer"
- },
- "locations": {
- "type": "array"
- },
- "hsts_enabled": {
- "type": "integer"
- },
- "hsts_subdomains": {
- "type": "integer"
- },
- "certificate": {
- "type": "object",
- "nullable": true
- },
- "owner": {
- "type": "object",
- "nullable": true
- },
- "access_list": {
- "type": "object",
- "nullable": true
- },
- "use_default_location": {
- "type": "boolean"
- },
- "ipv6": {
- "type": "boolean"
- }
- }
- },
- "ProxyHostsList": {
- "type": "array",
- "description": "Proxyn Hosts list",
- "items": {
- "$ref": "#/components/schemas/ProxyHostObject"
- }
- },
- "SettingObject": {
- "type": "object",
- "description": "Setting object",
- "required": ["id", "name", "description", "value", "meta"],
- "additionalProperties": false,
- "properties": {
- "id": {
- "type": "string",
- "description": "Setting ID",
- "minLength": 1,
- "example": "default-site"
- },
- "name": {
- "type": "string",
- "description": "Setting Display Name",
- "minLength": 1,
- "example": "Default Site"
- },
- "description": {
- "type": "string",
- "description": "Meaningful description",
- "minLength": 1,
- "example": "What to show when Nginx is hit with an unknown Host"
- },
- "value": {
- "description": "Value in almost any form",
- "example": "congratulations",
- "oneOf": [
- {
- "type": "string",
- "minLength": 1
- },
- {
- "type": "integer"
- },
- {
- "type": "object"
- },
- {
- "type": "number"
- },
- {
- "type": "array"
- }
- ]
- },
- "meta": {
- "description": "Extra metadata",
- "example": {},
- "type": "object"
- }
- }
- },
- "SettingsList": {
- "type": "array",
- "description": "Setting list",
- "items": {
- "$ref": "#/components/schemas/SettingObject"
- }
- },
- "UserObject": {
- "type": "object",
- "description": "User object",
- "required": ["id", "created_on", "modified_on", "is_disabled", "email", "name", "nickname", "avatar", "roles"],
- "additionalProperties": false,
- "properties": {
- "id": {
- "type": "integer",
- "description": "User ID",
- "minimum": 1,
- "example": 1
- },
- "created_on": {
- "type": "string",
- "description": "Created Date",
- "example": "2020-01-30T09:36:08.000Z"
- },
- "modified_on": {
- "type": "string",
- "description": "Modified Date",
- "example": "2020-01-30T09:41:04.000Z"
- },
- "is_disabled": {
- "type": "integer",
- "minimum": 0,
- "maximum": 1,
- "description": "Is user Disabled (0 = false, 1 = true)",
- "example": 0
- },
- "email": {
- "type": "string",
- "description": "Email",
- "minLength": 3,
- "example": "jc@jc21.com"
- },
- "name": {
- "type": "string",
- "description": "Name",
- "minLength": 1,
- "example": "Jamie Curnow"
- },
- "nickname": {
- "type": "string",
- "description": "Nickname",
- "example": "James"
- },
- "avatar": {
- "type": "string",
- "description": "Gravatar URL based on email, without scheme",
- "example": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm"
- },
- "roles": {
- "description": "Roles applied",
- "example": ["admin"],
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "UsersList": {
- "type": "array",
- "description": "User list",
- "items": {
- "$ref": "#/components/schemas/UserObject"
- }
- },
- "AuthObject": {
- "type": "object",
- "description": "Authentication Object",
- "required": ["type", "secret"],
- "properties": {
- "type": {
- "type": "string",
- "pattern": "^password$",
- "example": "password"
- },
- "current": {
- "type": "string",
- "minLength": 1,
- "maxLength": 99,
- "example": "iArhP1j7p1P6TA92FA2FMbbUGYqwcYzxC4AVEe12Wbi94FY9gNN62aKyF1shrvG4NycjjX9KfmDQiwkLZH1ZDR9xMjiG2QmoHXi"
- },
- "secret": {
- "type": "string",
- "minLength": 8,
- "maxLength": 99,
- "example": "5wdvvveVKkNNr8K7fSQKoUWbYyCZ2abtLaa1J5LzAvMfkGVcGBXHQ32iuPdeKdNfQVZiPKee3ZPKaGMvFR5t94QCeZbK3faSVYu"
- }
- }
- },
- "PermissionsObject": {
- "type": "object",
- "properties": {
- "visibility": {
- "type": "string",
- "description": "Visibility Type",
- "enum": ["all", "user"]
- },
- "access_lists": {
- "type": "string",
- "description": "Access Lists Permissions",
- "enum": ["hidden", "view", "manage"]
- },
- "dead_hosts": {
- "type": "string",
- "description": "404 Hosts Permissions",
- "enum": ["hidden", "view", "manage"]
- },
- "proxy_hosts": {
- "type": "string",
- "description": "Proxy Hosts Permissions",
- "enum": ["hidden", "view", "manage"]
- },
- "redirection_hosts": {
- "type": "string",
- "description": "Redirection Permissions",
- "enum": ["hidden", "view", "manage"]
- },
- "streams": {
- "type": "string",
- "description": "Streams Permissions",
- "enum": ["hidden", "view", "manage"]
- },
- "certificates": {
- "type": "string",
- "description": "Certificates Permissions",
- "enum": ["hidden", "view", "manage"]
- }
- }
- },
- "HostReportObject": {
- "type": "object",
- "properties": {
- "proxy": {
- "type": "integer",
- "description": "Proxy Hosts Count"
- },
- "redirection": {
- "type": "integer",
- "description": "Redirection Hosts Count"
- },
- "stream": {
- "type": "integer",
- "description": "Streams Count"
- },
- "dead": {
- "type": "integer",
- "description": "404 Hosts Count"
- }
- }
- }
- }
- }
-}
diff --git a/backend/eslint.config.mjs b/backend/eslint.config.mjs
deleted file mode 100644
index a4394115d..000000000
--- a/backend/eslint.config.mjs
+++ /dev/null
@@ -1,5 +0,0 @@
-import globals from 'globals';
-import pluginJs from '@eslint/js';
-import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
-
-export default [{ files: ['**/*.js'], languageOptions: { sourceType: 'commonjs' } }, { languageOptions: { globals: globals.node } }, pluginJs.configs.recommended, eslintPluginPrettierRecommended];
diff --git a/backend/index.js b/backend/index.js
deleted file mode 100755
index 13244e52a..000000000
--- a/backend/index.js
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/usr/bin/env node
-
-const logger = require('./logger').global;
-
-async function appStart() {
- const migrate = require('./migrate');
- const setup = require('./setup');
- const app = require('./app');
- const apiValidator = require('./lib/validator/api');
- const internalNginx = require('./internal/nginx');
- const internalCertificate = require('./internal/certificate');
- const internalIpRanges = require('./internal/ip_ranges');
-
- return migrate
- .latest()
- .then(setup)
- .then(() => {
- return apiValidator.loadSchemas;
- })
- .then(internalIpRanges.fetch)
- .then(() => {
- internalNginx.reload();
- internalCertificate.initTimer();
- internalIpRanges.initTimer();
-
- const server = app.listen(48693, '127.0.0.1', () => {
- logger.info('Backend PID ' + process.pid + ' listening on port 48693 ...');
-
- process.on('SIGTERM', () => {
- logger.info('PID ' + process.pid + ' received SIGTERM');
- server.close(() => {
- logger.info('Stopping.');
- process.exit(0);
- });
- });
- });
- })
- .catch((err) => {
- logger.error(err.message);
- setTimeout(appStart, 1000);
- });
-}
-
-try {
- appStart();
-} catch (err) {
- logger.error(err.message, err);
- process.exit(1);
-}
diff --git a/backend/internal/access-list.js b/backend/internal/access-list.js
deleted file mode 100644
index f2fe9fdb4..000000000
--- a/backend/internal/access-list.js
+++ /dev/null
@@ -1,511 +0,0 @@
-const _ = require('lodash');
-const fs = require('fs');
-const batchflow = require('batchflow');
-const logger = require('../logger').access;
-const error = require('../lib/error');
-const utils = require('../lib/utils');
-const accessListModel = require('../models/access_list');
-const accessListAuthModel = require('../models/access_list_auth');
-const accessListClientModel = require('../models/access_list_client');
-const proxyHostModel = require('../models/proxy_host');
-const internalAuditLog = require('./audit-log');
-const internalNginx = require('./nginx');
-
-function omissions() {
- return ['is_deleted'];
-}
-
-const internalAccessList = {
- /**
- * @param {Access} access
- * @param {Object} data
- * @returns {Promise}
- */
- create: (access, data) => {
- return access
- .can('access_lists:create', data)
- .then((/* access_data */) => {
- return accessListModel
- .query()
- .insertAndFetch({
- name: data.name,
- satisfy_any: data.satisfy_any,
- pass_auth: data.pass_auth,
- owner_user_id: access.token.getUserId(1),
- })
- .then(utils.omitRow(omissions()));
- })
- .then((row) => {
- data.id = row.id;
-
- const promises = [];
-
- // Now add the items
- data.items.map((item) => {
- promises.push(
- accessListAuthModel.query().insert({
- access_list_id: row.id,
- username: item.username,
- password: item.password,
- }),
- );
- });
-
- // Now add the clients
- if (typeof data.clients !== 'undefined' && data.clients) {
- data.clients.map((client) => {
- promises.push(
- accessListClientModel.query().insert({
- access_list_id: row.id,
- address: client.address,
- directive: client.directive,
- }),
- );
- });
- }
-
- return Promise.all(promises);
- })
- .then(() => {
- // re-fetch with expansions
- return internalAccessList.get(
- access,
- {
- id: data.id,
- expand: ['owner', 'items', 'clients', 'proxy_hosts.access_list.[clients,items]'],
- },
- true /* <- skip masking */,
- );
- })
- .then((row) => {
- // Audit log
- data.meta = _.assign({}, data.meta || {}, row.meta);
-
- return internalAccessList
- .build(row)
- .then(() => {
- if (row.proxy_host_count) {
- return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
- }
- })
- .then(() => {
- // Add to audit log
- return internalAuditLog.add(access, {
- action: 'created',
- object_type: 'access-list',
- object_id: row.id,
- meta: internalAccessList.maskItems(data),
- });
- })
- .then(() => {
- return internalAccessList.maskItems(row);
- });
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Integer} data.id
- * @param {String} [data.name]
- * @param {String} [data.items]
- * @return {Promise}
- */
- update: (access, data) => {
- return access
- .can('access_lists:update', data.id)
- .then((/* access_data */) => {
- return internalAccessList.get(access, { id: data.id });
- })
- .then((row) => {
- if (row.id !== data.id) {
- // Sanity check that something crazy hasn't happened
- throw new error.InternalValidationError('Access List could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
- }
- })
- .then(() => {
- // patch name if specified
- if (typeof data.name !== 'undefined' && data.name) {
- return accessListModel.query().where({ id: data.id }).patch({
- name: data.name,
- satisfy_any: data.satisfy_any,
- pass_auth: data.pass_auth,
- });
- }
- })
- .then(() => {
- // Check for items and add/update/remove them
- if (typeof data.items !== 'undefined' && data.items) {
- const promises = [];
- const items_to_keep = [];
-
- data.items.map(function (item) {
- if (item.password) {
- promises.push(
- accessListAuthModel.query().insert({
- access_list_id: data.id,
- username: item.username,
- password: item.password,
- }),
- );
- } else {
- // This was supplied with an empty password, which means keep it but don't change the password
- items_to_keep.push(item.username);
- }
- });
-
- const query = accessListAuthModel.query().delete().where('access_list_id', data.id);
-
- if (items_to_keep.length) {
- query.andWhere('username', 'NOT IN', items_to_keep);
- }
-
- return query.then(() => {
- // Add new items
- if (promises.length) {
- return Promise.all(promises);
- }
- });
- }
- })
- .then(() => {
- // Check for clients and add/update/remove them
- if (typeof data.clients !== 'undefined' && data.clients) {
- const promises = [];
-
- data.clients.map(function (client) {
- if (client.address) {
- promises.push(
- accessListClientModel.query().insert({
- access_list_id: data.id,
- address: client.address,
- directive: client.directive,
- }),
- );
- }
- });
-
- const query = accessListClientModel.query().delete().where('access_list_id', data.id);
-
- return query.then(() => {
- // Add new items
- if (promises.length) {
- return Promise.all(promises);
- }
- });
- }
- })
- .then(() => {
- // Add to audit log
- return internalAuditLog.add(access, {
- action: 'updated',
- object_type: 'access-list',
- object_id: data.id,
- meta: internalAccessList.maskItems(data),
- });
- })
- .then(() => {
- // re-fetch with expansions
- return internalAccessList.get(
- access,
- {
- id: data.id,
- expand: ['owner', 'items', 'clients', 'proxy_hosts.[certificate,access_list.[clients,items]]'],
- },
- true /* <- skip masking */,
- );
- })
- .then((row) => {
- return internalAccessList
- .build(row)
- .then(() => {
- if (row.proxy_host_count) {
- return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
- }
- })
- .then(internalNginx.reload)
- .then(() => {
- return internalAccessList.maskItems(row);
- });
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Integer} data.id
- * @param {Array} [data.expand]
- * @param {Array} [data.omit]
- * @param {Boolean} [skip_masking]
- * @return {Promise}
- */
- get: (access, data, skip_masking) => {
- if (typeof data === 'undefined') {
- data = {};
- }
-
- return access
- .can('access_lists:get', data.id)
- .then((access_data) => {
- const query = accessListModel.query().select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count')).joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0').where('access_list.is_deleted', 0).andWhere('access_list.id', data.id).allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]').first();
-
- if (access_data.permission_visibility !== 'all') {
- query.andWhere('access_list.owner_user_id', access.token.getUserId(1));
- }
-
- if (typeof data.expand !== 'undefined' && data.expand !== null) {
- query.withGraphFetched('[' + data.expand.join(', ') + ']');
- }
-
- return query.then(utils.omitRow(omissions()));
- })
- .then((row) => {
- if (!row) {
- throw new error.ItemNotFoundError(data.id);
- }
- if (!skip_masking && typeof row.items !== 'undefined' && row.items) {
- row = internalAccessList.maskItems(row);
- }
- // Custom omissions
- if (typeof data.omit !== 'undefined' && data.omit !== null) {
- row = _.omit(row, data.omit);
- }
- return row;
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Integer} data.id
- * @param {String} [data.reason]
- * @returns {Promise}
- */
- delete: (access, data) => {
- return access
- .can('access_lists:delete', data.id)
- .then(() => {
- return internalAccessList.get(access, { id: data.id, expand: ['proxy_hosts', 'items', 'clients'] });
- })
- .then((row) => {
- if (!row) {
- throw new error.ItemNotFoundError(data.id);
- }
-
- // 1. update row to be deleted
- // 2. update any proxy hosts that were using it (ignoring permissions)
- // 3. reconfigure those hosts
- // 4. audit log
-
- // 1. update row to be deleted
- return accessListModel
- .query()
- .where('id', row.id)
- .patch({
- is_deleted: 1,
- })
- .then(() => {
- // 2. update any proxy hosts that were using it (ignoring permissions)
- if (row.proxy_hosts) {
- return proxyHostModel
- .query()
- .where('access_list_id', '=', row.id)
- .patch({ access_list_id: 0 })
- .then(() => {
- // 3. reconfigure those hosts, then reload nginx
-
- // set the access_list_id to zero for these items
- row.proxy_hosts.map(function (val, idx) {
- row.proxy_hosts[idx].access_list_id = 0;
- });
-
- return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
- })
- .then(() => {
- return internalNginx.reload();
- });
- }
- })
- .then(() => {
- // delete the htpasswd file
- const htpasswd_file = internalAccessList.getFilename(row);
-
- try {
- fs.unlinkSync(htpasswd_file);
- } catch {
- // do nothing
- }
- })
- .then(() => {
- // 4. audit log
- return internalAuditLog.add(access, {
- action: 'deleted',
- object_type: 'access-list',
- object_id: row.id,
- meta: _.omit(internalAccessList.maskItems(row), ['is_deleted', 'proxy_hosts']),
- });
- });
- })
- .then(() => {
- return true;
- });
- },
-
- /**
- * All Lists
- *
- * @param {Access} access
- * @param {Array} [expand]
- * @param {String} [search_query]
- * @returns {Promise}
- */
- getAll: (access, expand, search_query) => {
- return access
- .can('access_lists:list')
- .then((access_data) => {
- const query = accessListModel.query().select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count')).joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0').where('access_list.is_deleted', 0).groupBy('access_list.id').allowGraph('[owner,items,clients]').orderBy('access_list.name', 'ASC');
-
- if (access_data.permission_visibility !== 'all') {
- query.andWhere('access_list.owner_user_id', access.token.getUserId(1));
- }
-
- // Query is used for searching
- if (typeof search_query === 'string') {
- query.where(function () {
- this.where('name', 'like', '%' + search_query + '%');
- });
- }
-
- if (typeof expand !== 'undefined' && expand !== null) {
- query.withGraphFetched('[' + expand.join(', ') + ']');
- }
-
- return query.then(utils.omitRows(omissions()));
- })
- .then((rows) => {
- if (rows) {
- rows.map(function (row, idx) {
- if (typeof row.items !== 'undefined' && row.items) {
- rows[idx] = internalAccessList.maskItems(row);
- }
- });
- }
-
- return rows;
- });
- },
-
- /**
- * Report use
- *
- * @param {Integer} user_id
- * @param {String} visibility
- * @returns {Promise}
- */
- getCount: (user_id, visibility) => {
- const query = accessListModel.query().count('id as count').where('is_deleted', 0);
-
- if (visibility !== 'all') {
- query.andWhere('owner_user_id', user_id);
- }
-
- return query.first().then((row) => {
- return parseInt(row.count, 10);
- });
- },
-
- /**
- * @param {Object} list
- * @returns {Object}
- */
- maskItems: (list) => {
- if (list && typeof list.items !== 'undefined') {
- list.items.map(function (val, idx) {
- let repeat_for = 8;
- let first_char = '*';
-
- if (typeof val.password !== 'undefined' && val.password) {
- repeat_for = val.password.length - 1;
- first_char = val.password.charAt(0);
- }
-
- list.items[idx].hint = first_char + '*'.repeat(repeat_for);
- list.items[idx].password = '';
- });
- }
-
- return list;
- },
-
- /**
- * @param {Object} list
- * @param {Integer} list.id
- * @returns {String}
- */
- getFilename: (list) => {
- return '/data/etc/access/' + list.id;
- },
-
- /**
- * @param {Object} list
- * @param {Integer} list.id
- * @param {String} list.name
- * @param {Array} list.items
- * @returns {Promise}
- */
- build: (list) => {
- logger.info('Building Access file #' + list.id + ' for: ' + list.name);
-
- return new Promise((resolve, reject) => {
- const htpasswd_file = internalAccessList.getFilename(list);
-
- // 1. remove any existing access file
- try {
- fs.unlinkSync(htpasswd_file);
- } catch {
- // do nothing
- }
-
- // 2. create empty access file
- try {
- fs.writeFileSync(htpasswd_file, '', { encoding: 'utf8' });
- resolve(htpasswd_file);
- } catch (err) {
- reject(err);
- }
- }).then((htpasswd_file) => {
- // 3. generate password for each user
- if (list.items.length) {
- return new Promise((resolve, reject) => {
- batchflow(list.items)
- .sequential()
- .each((i, item, next) => {
- if (typeof item.password !== 'undefined' && item.password.length) {
- logger.info('Adding: ' + item.username);
-
- utils
- .execFile('htpasswd', ['-b', htpasswd_file, item.username, item.password])
- .then((/* result */) => {
- next();
- })
- .catch((err) => {
- logger.error(err);
- next(err);
- });
- }
- })
- .error((err) => {
- logger.error(err);
- reject(err);
- })
- .end((results) => {
- logger.success('Built Access file #' + list.id + ' for: ' + list.name);
- resolve(results);
- });
- });
- }
- });
- },
-};
-
-module.exports = internalAccessList;
diff --git a/backend/internal/audit-log.js b/backend/internal/audit-log.js
deleted file mode 100644
index cd6c61074..000000000
--- a/backend/internal/audit-log.js
+++ /dev/null
@@ -1,71 +0,0 @@
-const error = require('../lib/error');
-const auditLogModel = require('../models/audit-log');
-
-const internalAuditLog = {
- /**
- * All logs
- *
- * @param {Access} access
- * @param {Array} [expand]
- * @param {String} [search_query]
- * @returns {Promise}
- */
- getAll: (access, expand, search_query) => {
- return access.can('auditlog:list').then(() => {
- const query = auditLogModel.query().orderBy('created_on', 'DESC').orderBy('id', 'DESC').limit(100).allowGraph('[user]');
-
- // Query is used for searching
- if (typeof search_query === 'string') {
- query.where(function () {
- this.where('meta', 'like', '%' + search_query + '%');
- });
- }
-
- if (typeof expand !== 'undefined' && expand !== null) {
- query.withGraphFetched('[' + expand.join(', ') + ']');
- }
-
- return query;
- });
- },
-
- /**
- * This method should not be publicly used, it doesn't check certain things. It will be assumed
- * that permission to add to audit log is already considered, however the access token is used for
- * default user id determination.
- *
- * @param {Access} access
- * @param {Object} data
- * @param {String} data.action
- * @param {Number} [data.user_id]
- * @param {Number} [data.object_id]
- * @param {Number} [data.object_type]
- * @param {Object} [data.meta]
- * @returns {Promise}
- */
- add: (access, data) => {
- return new Promise((resolve, reject) => {
- // Default the user id
- if (typeof data.user_id === 'undefined' || !data.user_id) {
- data.user_id = access.token.getUserId(1);
- }
-
- if (typeof data.action === 'undefined' || !data.action) {
- reject(new error.InternalValidationError('Audit log entry must contain an Action'));
- } else {
- // Make sure at least 1 of the IDs are set and action
- resolve(
- auditLogModel.query().insert({
- user_id: data.user_id,
- action: data.action,
- object_type: data.object_type || '',
- object_id: data.object_id || 0,
- meta: data.meta || {},
- }),
- );
- }
- });
- },
-};
-
-module.exports = internalAuditLog;
diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js
deleted file mode 100644
index 6ed822be8..000000000
--- a/backend/internal/certificate.js
+++ /dev/null
@@ -1,1132 +0,0 @@
-const _ = require('lodash');
-const fs = require('fs');
-const https = require('https');
-const moment = require('moment');
-const logger = require('../logger').ssl;
-const error = require('../lib/error');
-const utils = require('../lib/utils');
-const certificateModel = require('../models/certificate');
-const dnsPlugins = require('../certbot-dns-plugins.json');
-const internalAuditLog = require('./audit-log');
-const internalNginx = require('./nginx');
-const certbot = require('../lib/certbot');
-const archiver = require('archiver');
-const crypto = require('crypto');
-const path = require('path');
-const { isArray } = require('lodash');
-
-const certbotConfig = '/data/tls/certbot/config.ini';
-const certbotCommand = 'certbot --logs-dir /tmp/certbot-log --work-dir /tmp/certbot-work --config-dir /data/tls/certbot';
-
-function omissions() {
- return ['is_deleted'];
-}
-
-const internalCertificate = {
- allowedSslFiles: ['certificate', 'certificate_key', 'intermediate_certificate'],
- intervalTimeout: 1000 * 60 * 60 * Number(process.env.CRT),
- interval: null,
- intervalProcessing: false,
-
- initTimer: () => {
- logger.info('Certbot Renewal Timer initialized');
- internalCertificate.interval = setInterval(internalCertificate.processExpiringHosts, internalCertificate.intervalTimeout);
- },
-
- /**
- * Triggered by a timer, this will check for expiring hosts and renew their tls certs if required
- */
- processExpiringHosts: () => {
- if (!internalCertificate.intervalProcessing) {
- internalCertificate.intervalProcessing = true;
- logger.info('Renewing TLS certs close to expiry...');
-
- const cmd = certbotCommand + ' renew --quiet ' + '--config "' + certbotConfig + '" ' + '--preferred-challenges "dns,http" ' + '--no-random-sleep-on-renew';
-
- return utils
- .exec(cmd)
- .then((result) => {
- if (result) {
- logger.info('Renew Result: ' + result);
- }
-
- return internalNginx.reload().then(() => {
- logger.info('Renew Complete');
- return result;
- });
- })
- .then(() => {
- // Now go and fetch all the certbot certs from the db and query the files and update expiry times
- return certificateModel
- .query()
- .where('is_deleted', 0)
- .andWhere('provider', 'letsencrypt')
- .then((certificates) => {
- if (certificates && certificates.length) {
- const promises = [];
-
- certificates.map(function (certificate) {
- promises.push(
- internalCertificate
- .getCertificateInfoFromFile('/data/tls/certbot/live/npm-' + certificate.id + '/fullchain.pem')
- .then((cert_info) => {
- return certificateModel
- .query()
- .where('id', certificate.id)
- .andWhere('provider', 'letsencrypt')
- .patch({
- expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss'),
- });
- })
- .catch((err) => {
- // Don't want to stop the train here, just log the error
- logger.error(err.message);
- }),
- );
- });
-
- return Promise.all(promises);
- }
- });
- })
- .then(() => {
- internalCertificate.intervalProcessing = false;
- })
- .catch((err) => {
- logger.error(err);
- internalCertificate.intervalProcessing = false;
- });
- }
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @returns {Promise}
- */
- create: (access, data) => {
- return access
- .can('certificates:create', data)
- .then(() => {
- data.owner_user_id = access.token.getUserId(1);
-
- if (data.provider === 'letsencrypt') {
- data.nice_name = data.domain_names.join(', ');
- }
-
- return certificateModel.query().insertAndFetch(data).then(utils.omitRow(omissions()));
- })
- .then((certificate) => {
- if (certificate.provider === 'letsencrypt') {
- // Request a new Cert using Certbot. Let the fun begin.
- if (certificate.meta.dns_challenge) {
- return internalCertificate
- .requestLetsEncryptSslWithDnsChallenge(certificate)
- .then(() => {
- return certificate;
- })
- .catch((err) => {
- // In the event of failure, throw err back
- throw err;
- });
- } else {
- return internalCertificate
- .requestLetsEncryptSsl(certificate)
- .then(() => {
- return certificate;
- })
- .catch((err) => {
- // In the event of failure, throw err back
- throw err;
- });
- }
- } else {
- return certificate;
- }
- })
- .then((certificate) => {
- if (certificate.provider === 'letsencrypt') {
- // At this point, the certbot cert should exist on disk.
- // Lets get the expiry date from the file and update the row silently
- return internalCertificate
- .getCertificateInfoFromFile('/data/tls/certbot/live/npm-' + certificate.id + '/fullchain.pem')
- .then((cert_info) => {
- return certificateModel
- .query()
- .patchAndFetchById(certificate.id, {
- expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss'),
- })
- .then((saved_row) => {
- // Add cert data for audit log
- saved_row.meta = _.assign({}, saved_row.meta, {
- letsencrypt_certificate: cert_info,
- });
-
- return saved_row;
- });
- })
- .catch(async (error) => {
- // Delete the certificate from the database if it was not created successfully
- await certificateModel.query().deleteById(certificate.id);
-
- throw error;
- });
- } else {
- return certificate;
- }
- })
- .then((certificate) => {
- data.meta = _.assign({}, data.meta || {}, certificate.meta);
-
- // Add to audit log
- return internalAuditLog
- .add(access, {
- action: 'created',
- object_type: 'certificate',
- object_id: certificate.id,
- meta: data,
- })
- .then(() => {
- return certificate;
- });
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @param {String} [data.email]
- * @param {String} [data.name]
- * @return {Promise}
- */
- update: (access, data) => {
- return access
- .can('certificates:update', data.id)
- .then((/* access_data */) => {
- return internalCertificate.get(access, { id: data.id });
- })
- .then((row) => {
- if (row.id !== data.id) {
- // Sanity check that something crazy hasn't happened
- throw new error.InternalValidationError('Certificate could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
- }
-
- return certificateModel
- .query()
- .patchAndFetchById(row.id, data)
- .then(utils.omitRow(omissions()))
- .then((saved_row) => {
- saved_row.meta = internalCertificate.cleanMeta(saved_row.meta);
- data.meta = internalCertificate.cleanMeta(data.meta);
-
- // Add row.nice_name for custom certs
- if (saved_row.provider === 'other') {
- data.nice_name = saved_row.nice_name;
- }
-
- // Add to audit log
- return internalAuditLog
- .add(access, {
- action: 'updated',
- object_type: 'certificate',
- object_id: row.id,
- meta: _.omit(data, ['expires_on']), // this prevents json circular reference because expires_on might be raw
- })
- .then(() => {
- return saved_row;
- });
- });
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @param {Array} [data.expand]
- * @param {Array} [data.omit]
- * @return {Promise}
- */
- get: (access, data) => {
- if (typeof data === 'undefined') {
- data = {};
- }
-
- return access
- .can('certificates:get', data.id)
- .then((access_data) => {
- const query = certificateModel.query().where('is_deleted', 0).andWhere('id', data.id).allowGraph('[owner]').first();
-
- if (access_data.permission_visibility !== 'all') {
- query.andWhere('owner_user_id', access.token.getUserId(1));
- }
-
- if (typeof data.expand !== 'undefined' && data.expand !== null) {
- query.withGraphFetched('[' + data.expand.join(', ') + ']');
- }
-
- return query.then(utils.omitRow(omissions()));
- })
- .then((row) => {
- if (!row) {
- throw new error.ItemNotFoundError(data.id);
- }
- // Custom omissions
- if (typeof data.omit !== 'undefined' && data.omit !== null) {
- row = _.omit(row, data.omit);
- }
- return row;
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @returns {Promise}
- */
- download: (access, data) => {
- return new Promise((resolve, reject) => {
- access
- .can('certificates:get', data)
- .then(() => {
- return internalCertificate.get(access, data);
- })
- .then((certificate) => {
- if (certificate.provider === 'letsencrypt') {
- const zipDirectory = '/data/tls/certbot/live/npm-' + data.id;
-
- if (!fs.existsSync(zipDirectory)) {
- throw new error.ItemNotFoundError('Certificate ' + certificate.nice_name + ' does not exists');
- }
-
- const certFiles = fs
- .readdirSync(zipDirectory)
- .filter((fn) => fn.endsWith('.pem'))
- .map((fn) => fs.realpathSync(path.join(zipDirectory, fn)));
- const downloadName = 'npm-' + data.id + '-' + `${Date.now()}.zip`;
- const opName = '/tmp/' + downloadName;
- internalCertificate
- .zipFiles(certFiles, opName)
- .then(() => {
- logger.debug('zip completed : ', opName);
- const resp = {
- fileName: opName,
- };
- resolve(resp);
- })
- .catch((err) => reject(err));
- } else {
- throw new error.ValidationError('Only Certbot certificates can be downloaded');
- }
- })
- .catch((err) => reject(err));
- });
- },
-
- /**
- * @param {String} source
- * @param {String} out
- * @returns {Promise}
- */
- zipFiles(source, out) {
- const archive = archiver('zip', { zlib: { level: 9 } });
- const stream = fs.createWriteStream(out);
-
- return new Promise((resolve, reject) => {
- source.map((fl) => {
- const fileName = path.basename(fl);
- logger.debug(fl, 'added to certificate zip');
- archive.file(fl, { name: fileName });
- });
- archive.on('error', (err) => reject(err)).pipe(stream);
-
- stream.on('close', () => resolve());
- archive.finalize();
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @param {String} [data.reason]
- * @returns {Promise}
- */
- delete: (access, data) => {
- return access
- .can('certificates:delete', data.id)
- .then(() => {
- return internalCertificate.get(access, { id: data.id });
- })
- .then((row) => {
- if (!row) {
- throw new error.ItemNotFoundError(data.id);
- }
-
- return certificateModel
- .query()
- .where('id', row.id)
- .patch({
- is_deleted: 1,
- })
- .then(() => {
- // Add to audit log
- row.meta = internalCertificate.cleanMeta(row.meta);
-
- return internalAuditLog.add(access, {
- action: 'deleted',
- object_type: 'certificate',
- object_id: row.id,
- meta: _.omit(row, omissions()),
- });
- })
- .then(() => {
- if (row.provider === 'letsencrypt') {
- // Revoke the cert
- return internalCertificate.revokeLetsEncryptSsl(row);
- }
- });
- })
- .then(() => {
- return true;
- });
- },
-
- /**
- * All Certs
- *
- * @param {Access} access
- * @param {Array} [expand]
- * @param {String} [search_query]
- * @returns {Promise}
- */
- getAll: (access, expand, search_query) => {
- return access.can('certificates:list').then((access_data) => {
- const query = certificateModel.query().where('is_deleted', 0).groupBy('id').allowGraph('[owner]').orderBy('nice_name', 'ASC');
-
- if (access_data.permission_visibility !== 'all') {
- query.andWhere('owner_user_id', access.token.getUserId(1));
- }
-
- // Query is used for searching
- if (typeof search_query === 'string') {
- query.where(function () {
- this.where('nice_name', 'like', '%' + search_query + '%');
- });
- }
-
- if (typeof expand !== 'undefined' && expand !== null) {
- query.withGraphFetched('[' + expand.join(', ') + ']');
- }
-
- return query.then(utils.omitRows(omissions()));
- });
- },
-
- /**
- * Report use
- *
- * @param {Number} user_id
- * @param {String} visibility
- * @returns {Promise}
- */
- getCount: (user_id, visibility) => {
- const query = certificateModel.query().count('id as count').where('is_deleted', 0);
-
- if (visibility !== 'all') {
- query.andWhere('owner_user_id', user_id);
- }
-
- return query.first().then((row) => {
- return parseInt(row.count, 10);
- });
- },
-
- /**
- * @param {Object} certificate
- * @returns {Promise}
- */
- writeCustomCert: (certificate) => {
- logger.info('Writing Custom Certificate:', certificate);
-
- const dir = '/data/tls/custom/npm-' + certificate.id;
-
- return new Promise((resolve, reject) => {
- if (certificate.provider === 'letsencrypt') {
- reject(new Error('Refusing to write certbot certs here'));
- return;
- }
-
- let certData = certificate.meta.certificate;
- if (typeof certificate.meta.intermediate_certificate !== 'undefined') {
- certData = certData + '\n' + certificate.meta.intermediate_certificate;
- }
-
- try {
- if (!fs.existsSync(dir)) {
- fs.mkdirSync(dir);
- }
- } catch (err) {
- reject(err);
- return;
- }
-
- fs.writeFile(dir + '/fullchain.pem', certData, function (err) {
- if (err) {
- reject(err);
- } else {
- resolve();
- }
- });
- }).then(() => {
- return new Promise((resolve, reject) => {
- fs.writeFile(dir + '/privkey.pem', certificate.meta.certificate_key, function (err) {
- if (err) {
- reject(err);
- } else {
- resolve();
- }
- });
- });
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Array} data.domain_names
- * @param {String} data.meta.letsencrypt_email
- * @param {Boolean} data.meta.letsencrypt_agree
- * @returns {Promise}
- */
- createQuickCertificate: (access, data) => {
- return internalCertificate.create(access, {
- provider: 'letsencrypt',
- domain_names: data.domain_names,
- meta: data.meta,
- });
- },
-
- /**
- * Validates that the certs provided are good.
- * No access required here, nothing is changed or stored.
- *
- * @param {Object} data
- * @param {Object} data.files
- * @returns {Promise}
- */
- validate: (data) => {
- return new Promise((resolve) => {
- // Put file contents into an object
- const files = {};
- _.map(data.files, (file, name) => {
- if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) {
- files[name] = file.data.toString();
- }
- });
-
- resolve(files);
- }).then((files) => {
- // For each file, create a temp file and write the contents to it
- // Then test it depending on the file type
- const promises = [];
- _.map(files, (content, type) => {
- promises.push(
- new Promise((resolve) => {
- if (type === 'certificate_key') {
- resolve(internalCertificate.checkPrivateKey(content));
- } else {
- // this should handle `certificate` and intermediate certificate
- resolve(internalCertificate.getCertificateInfo(content, true));
- }
- }).then((res) => {
- return { [type]: res };
- }),
- );
- });
-
- return Promise.all(promises).then((files) => {
- let data = {};
-
- _.each(files, (file) => {
- data = _.assign({}, data, file);
- });
-
- return data;
- });
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @param {Object} data.files
- * @returns {Promise}
- */
- upload: (access, data) => {
- return internalCertificate.get(access, { id: data.id }).then((row) => {
- if (row.provider !== 'other') {
- throw new error.ValidationError('Cannot upload certificates for this type of provider');
- }
-
- return internalCertificate
- .validate(data)
- .then((validations) => {
- if (typeof validations.certificate === 'undefined') {
- throw new error.ValidationError('Certificate file was not provided');
- }
-
- _.map(data.files, (file, name) => {
- if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) {
- row.meta[name] = file.data.toString();
- }
- });
-
- // TODO: This uses a mysql only raw function that won't translate to postgres
- return internalCertificate
- .update(access, {
- id: data.id,
- expires_on: moment(validations.certificate.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss'),
- domain_names: [validations.certificate.cn],
- meta: _.clone(row.meta), // Prevent the update method from changing this value that we'll use later
- })
- .then((certificate) => {
- certificate.meta = row.meta;
- return internalCertificate.writeCustomCert(certificate);
- });
- })
- .then(() => {
- return _.pick(row.meta, internalCertificate.allowedSslFiles);
- });
- });
- },
-
- /**
- * Uses the openssl command to validate the private key.
- * It will save the file to disk first, then run commands on it, then delete the file.
- *
- * @param {String} private_key This is the entire key contents as a string
- */
- checkPrivateKey: (private_key) => {
- const randomName = crypto.randomBytes(8).toString('hex');
- const filepath = path.join('/tmp', 'certificate_' + randomName);
- fs.writeFileSync(filepath, private_key);
- return new Promise((resolve, reject) => {
- const failTimeout = setTimeout(() => {
- reject(new error.ValidationError('Result Validation Error: Validation timed out. This could be due to the key being passphrase-protected.'));
- }, 10000);
- utils
- .exec('openssl pkey -in ' + filepath + ' -check -noout 2>&1 ')
- .then((result) => {
- clearTimeout(failTimeout);
- if (!result.toLowerCase().includes('key is valid')) {
- reject(new error.ValidationError('Result Validation Error: ' + result));
- }
- fs.unlinkSync(filepath);
- resolve(true);
- })
- .catch((err) => {
- clearTimeout(failTimeout);
- fs.unlinkSync(filepath);
- reject(new error.ValidationError('Certificate Key is not valid (' + err.message + ')', err));
- });
- });
- },
-
- /**
- * Uses the openssl command to both validate and get info out of the certificate.
- * It will save the file to disk first, then run commands on it, then delete the file.
- *
- * @param {String} certificate This is the entire cert contents as a string
- * @param {Boolean} [throw_expired] Throw when the certificate is out of date
- */
- getCertificateInfo: (certificate, throw_expired) => {
- const randomName = crypto.randomBytes(8).toString('hex');
- const filepath = path.join('/tmp', 'certificate_' + randomName);
- fs.writeFileSync(filepath, certificate);
- return internalCertificate
- .getCertificateInfoFromFile(filepath, throw_expired)
- .then((certData) => {
- fs.unlinkSync(filepath);
- return certData;
- })
- .catch((err) => {
- fs.unlinkSync(filepath);
- throw err;
- });
- },
-
- /**
- * Uses the openssl command to both validate and get info out of the certificate.
- * It will save the file to disk first, then run commands on it, then delete the file.
- *
- * @param {String} certificate_file The file location on disk
- * @param {Boolean} [throw_expired] Throw when the certificate is out of date
- */
- getCertificateInfoFromFile: (certificate_file, throw_expired) => {
- const certData = {};
-
- return utils
- .exec('openssl x509 -in ' + certificate_file + ' -subject -noout')
- .then((result) => {
- // subject=CN = something.example.com
- const regex = /(?:subject=)?[^=]+=\s*(\S+)/gim;
- const match = regex.exec(result);
-
- if (typeof match[1] === 'undefined') {
- throw new error.ValidationError('Could not determine subject from certificate: ' + result);
- }
-
- certData.cn = match[1];
- })
- .then(() => {
- return utils.exec('openssl x509 -in ' + certificate_file + ' -issuer -noout');
- })
- .then((result) => {
- const regex = /^(?:issuer=)?(.*)$/gim;
- const match = regex.exec(result);
-
- if (typeof match[1] === 'undefined') {
- throw new error.ValidationError('Could not determine issuer from certificate: ' + result);
- }
-
- certData.issuer = match[1];
- })
- .then(() => {
- return utils.exec('openssl x509 -in ' + certificate_file + ' -dates -noout');
- })
- .then((result) => {
- // notBefore=Jul 14 04:04:29 2018 GMT
- // notAfter=Oct 12 04:04:29 2018 GMT
- let validFrom = null;
- let validTo = null;
-
- const lines = result.split('\n');
- lines.map(function (str) {
- const regex = /^(\S+)=(.*)$/gim;
- const match = regex.exec(str.trim());
-
- if (match && typeof match[2] !== 'undefined') {
- const date = parseInt(moment(match[2], 'MMM DD HH:mm:ss YYYY z').format('X'), 10);
-
- if (match[1].toLowerCase() === 'notbefore') {
- validFrom = date;
- } else if (match[1].toLowerCase() === 'notafter') {
- validTo = date;
- }
- }
- });
-
- if (!validFrom || !validTo) {
- throw new error.ValidationError('Could not determine dates from certificate: ' + result);
- }
-
- if (throw_expired && validTo < parseInt(moment().format('X'), 10)) {
- throw new error.ValidationError('Certificate has expired');
- }
-
- certData.dates = {
- from: validFrom,
- to: validTo,
- };
-
- return certData;
- })
- .catch((err) => {
- throw new error.ValidationError('Certificate is not valid (' + err.message + ')', err);
- });
- },
-
- /**
- * Cleans the tls keys from the meta object and sets them to "true"
- *
- * @param {Object} meta
- * @param {Boolean} [remove]
- * @returns {Object}
- */
- cleanMeta: function (meta, remove) {
- internalCertificate.allowedSslFiles.map((key) => {
- if (typeof meta[key] !== 'undefined' && meta[key]) {
- if (remove) {
- delete meta[key];
- } else {
- meta[key] = true;
- }
- }
- });
-
- return meta;
- },
-
- /**
- * Request a certificate using the http challenge
- * @param {Object} certificate the certificate row
- * @returns {Promise}
- */
- requestLetsEncryptSsl: (certificate) => {
- logger.info('Requesting Certbot certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
-
- let cmd = certbotCommand + ' certonly ' + '--config "' + certbotConfig + '" ' + '--cert-name "npm-' + certificate.id + '" ' + '--authenticator webroot ' + '--preferred-challenges "http,dns" ' + '--domains "' + certificate.domain_names.join(',') + '"';
-
- if (certificate.meta.letsencrypt_email === '') {
- cmd = cmd + ' --register-unsafely-without-email ';
- } else {
- cmd = cmd + ' --email "' + certificate.meta.letsencrypt_email + '" ';
- }
-
- logger.info('Command:', cmd);
-
- return utils.exec(cmd).then((result) => {
- logger.success(result);
- return result;
- });
- },
-
- /**
- * @param {Object} certificate the certificate row
- * @param {String} dns_provider the dns provider name (key used in `certbot-dns-plugins.json`)
- * @param {String | null} credentials the content of this providers credentials file
- * @param {String} propagation_seconds the time to wait until the dns record should be changed
- * @returns {Promise}
- */
- requestLetsEncryptSslWithDnsChallenge: async (certificate) => {
- await certbot.installPlugin(certificate.meta.dns_provider);
- const dnsPlugin = dnsPlugins[certificate.meta.dns_provider];
- logger.info(`Requesting Certbot certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
-
- const credentialsLocation = '/data/tls/certbot/credentials/credentials-' + certificate.id;
- fs.mkdirSync('/data/tls/certbot/credentials', { recursive: true });
- fs.writeFileSync(credentialsLocation, certificate.meta.dns_provider_credentials, { mode: 0o600 });
-
- let mainCmd = certbotCommand + ' certonly ' + '--config "' + certbotConfig + '" ' + '--cert-name "npm-' + certificate.id + '" ' + '--domains "' + certificate.domain_names.join(',') + '" ' + '--authenticator ' + dnsPlugin.full_plugin_name + ' ' + '--' + dnsPlugin.full_plugin_name + '-credentials "' + credentialsLocation + '"' + (certificate.meta.propagation_seconds !== undefined ? ' --' + dnsPlugin.full_plugin_name + '-propagation-seconds ' + certificate.meta.propagation_seconds : '');
-
- if (certificate.meta.letsencrypt_email === '') {
- mainCmd = mainCmd + ' --register-unsafely-without-email ';
- } else {
- mainCmd = mainCmd + ' --email "' + certificate.meta.letsencrypt_email + '" ';
- }
-
- logger.info('Command:', mainCmd);
-
- try {
- const result = await utils.exec(mainCmd);
- logger.info(result);
- return result;
- } catch (err) {
- // Don't fail if file does not exist, so no need for action in the callback
- fs.unlink(credentialsLocation, () => {});
- throw err;
- }
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @returns {Promise}
- */
- renew: (access, data) => {
- return access
- .can('certificates:update', data)
- .then(() => {
- return internalCertificate.get(access, data);
- })
- .then((certificate) => {
- if (certificate.provider === 'letsencrypt') {
- const renewMethod = certificate.meta.dns_challenge ? internalCertificate.renewLetsEncryptSslWithDnsChallenge : internalCertificate.renewLetsEncryptSsl;
-
- return renewMethod(certificate)
- .then(() => {
- return internalCertificate.getCertificateInfoFromFile('/data/tls/certbot/live/npm-' + certificate.id + '/fullchain.pem');
- })
- .then((cert_info) => {
- return certificateModel.query().patchAndFetchById(certificate.id, {
- expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss'),
- });
- })
- .then((updated_certificate) => {
- // Add to audit log
- return internalAuditLog
- .add(access, {
- action: 'renewed',
- object_type: 'certificate',
- object_id: updated_certificate.id,
- meta: updated_certificate,
- })
- .then(() => {
- return updated_certificate;
- });
- });
- } else {
- throw new error.ValidationError('Only Certbot certificates can be renewed');
- }
- });
- },
-
- /**
- * @param {Object} certificate the certificate row
- * @returns {Promise}
- */
- renewLetsEncryptSsl: async (certificate) => {
- logger.info('Renewing Certbot certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
-
- const cmdr = certbotCommand + ' revoke ' + '--config "' + certbotConfig + '" ' + '--cert-path "/data/tls/certbot/live/npm-' + certificate.id + '/privkey.pem" ' + '--cert-path "/data/tls/certbot/live/npm-' + certificate.id + '/fullchain.pem" --no-delete-after-revoke';
-
- logger.info('Command:', cmdr);
-
- const revokeResult = await utils.exec(cmdr);
- logger.info(revokeResult);
-
- const cmd = certbotCommand + ' renew --force-renewal ' + '--config "' + certbotConfig + '" ' + '--cert-name "npm-' + certificate.id + '" ' + '--preferred-challenges "http,dns" ' + '--no-random-sleep-on-renew';
-
- logger.info('Command:', cmd);
-
- const renewResult = await utils.exec(cmd);
- logger.info(renewResult);
-
- return renewResult;
- },
-
- /**
- * @param {Object} certificate the certificate row
- * @returns {Promise}
- */
- renewLetsEncryptSslWithDnsChallenge: async (certificate) => {
- const dnsPlugin = dnsPlugins[certificate.meta.dns_provider];
-
- if (!dnsPlugin) {
- throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`);
- }
-
- logger.info(`Renewing Certbot certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
-
- const mainCmdr = certbotCommand + ' revoke ' + '--config "' + certbotConfig + '" ' + '--cert-path "/data/tls/certbot/live/npm-' + certificate.id + '/privkey.pem" ' + '--cert-path "/data/tls/certbot/live/npm-' + certificate.id + '/fullchain.pem" --no-delete-after-revoke';
-
- logger.info('Command:', mainCmdr);
-
- const revokeResult = await utils.exec(mainCmdr);
- logger.info(revokeResult);
-
- const mainCmd = certbotCommand + ' renew --force-renewal ' + '--config "' + certbotConfig + '" ' + '--cert-name "npm-' + certificate.id + '" ' + '--preferred-challenges "dns,http" ' + '--no-random-sleep-on-renew';
-
- logger.info('Command:', mainCmd);
-
- const renewResult = await utils.exec(mainCmd);
- logger.info(renewResult);
-
- return renewResult;
- },
-
- /**
- * @param {Object} certificate the certificate row
- * @param {Boolean} [throw_errors]
- * @returns {Promise}
- */
- revokeLetsEncryptSsl: (certificate, throw_errors) => {
- logger.info('Revoking Certbot certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
-
- const mainCmd = certbotCommand + ' revoke ' + '--config "' + certbotConfig + '" ' + '--cert-path "/data/tls/certbot/live/npm-' + certificate.id + '/privkey.pem" ' + '--cert-path "/data/tls/certbot/live/npm-' + certificate.id + '/fullchain.pem" ' + '--delete-after-revoke';
-
- return utils
- .exec(mainCmd)
- .then(async (result) => {
- fs.rm('/data/tls/certbot/credentials/credentials-' + certificate.id, { force: true }, (err) => {
- if (err) {
- logger.error('Error deleting credentials:', err.message);
- if (throw_errors) {
- throw err;
- }
- } else {
- logger.info('Credentials file deleted successfully');
- }
- });
- logger.info(result);
- return result;
- })
- .catch((err) => {
- logger.error(err.message);
-
- if (throw_errors) {
- throw err;
- }
- });
- },
-
- /**
- * @param {Object} certificate
- * @returns {Boolean}
- */
- hasLetsEncryptSslCerts: (certificate) => {
- const letsencryptPath = '/data/tls/certbot/live/npm-' + certificate.id;
-
- return fs.existsSync(letsencryptPath + '/fullchain.pem') && fs.existsSync(letsencryptPath + '/privkey.pem');
- },
-
- /**
- * @param {Object} in_use_result
- * @param {Number} in_use_result.total_count
- * @param {Array} in_use_result.proxy_hosts
- * @param {Array} in_use_result.redirection_hosts
- * @param {Array} in_use_result.dead_hosts
- */
- disableInUseHosts: (in_use_result) => {
- if (in_use_result.total_count) {
- const promises = [];
-
- if (in_use_result.proxy_hosts.length) {
- promises.push(internalNginx.bulkDeleteConfigs('proxy_host', in_use_result.proxy_hosts));
- }
-
- if (in_use_result.redirection_hosts.length) {
- promises.push(internalNginx.bulkDeleteConfigs('redirection_host', in_use_result.redirection_hosts));
- }
-
- if (in_use_result.dead_hosts.length) {
- promises.push(internalNginx.bulkDeleteConfigs('dead_host', in_use_result.dead_hosts));
- }
-
- return Promise.all(promises);
- } else {
- return Promise.resolve();
- }
- },
-
- /**
- * @param {Object} in_use_result
- * @param {Number} in_use_result.total_count
- * @param {Array} in_use_result.proxy_hosts
- * @param {Array} in_use_result.redirection_hosts
- * @param {Array} in_use_result.dead_hosts
- */
- enableInUseHosts: (in_use_result) => {
- if (in_use_result.total_count) {
- const promises = [];
-
- if (in_use_result.proxy_hosts.length) {
- promises.push(internalNginx.bulkGenerateConfigs('proxy_host', in_use_result.proxy_hosts));
- }
-
- if (in_use_result.redirection_hosts.length) {
- promises.push(internalNginx.bulkGenerateConfigs('redirection_host', in_use_result.redirection_hosts));
- }
-
- if (in_use_result.dead_hosts.length) {
- promises.push(internalNginx.bulkGenerateConfigs('dead_host', in_use_result.dead_hosts));
- }
-
- return Promise.all(promises);
- } else {
- return Promise.resolve();
- }
- },
-
- testHttpsChallenge: async (access, domains) => {
- await access.can('certificates:list');
-
- if (!isArray(domains)) {
- throw new error.InternalValidationError('Domains must be an array of strings');
- }
- if (domains.length === 0) {
- throw new error.InternalValidationError('No domains provided');
- }
-
- // Create a test challenge file
- const testChallengeDir = '/tmp/acme-challenge/.well-known/acme-challenge';
- const testChallengeFile = testChallengeDir + '/test-challenge';
- fs.mkdirSync(testChallengeDir, { recursive: true });
- fs.writeFileSync(testChallengeFile, 'Success', { encoding: 'utf8' });
-
- async function performTestForDomain(domain) {
- logger.info('Testing http challenge for ' + domain);
- const url = `http://${domain}/.well-known/acme-challenge/test-challenge`;
- const formBody = `method=G&url=${encodeURI(url)}&bodytype=T&locationid=10`;
- const options = {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded',
- 'Content-Length': Buffer.byteLength(formBody),
- Connection: 'keep-alive',
- 'User-Agent': 'NPMplus',
- Accept: '*/*',
- },
- };
-
- const result = await new Promise((resolve) => {
- const req = https.request('https://www.site24x7.com/tools/restapi-tester', options, function (res) {
- let responseBody = '';
-
- res.on('data', (chunk) => (responseBody = responseBody + chunk));
- res.on('end', function () {
- try {
- const parsedBody = JSON.parse(responseBody + '');
- if (res.statusCode !== 200) {
- logger.warn(`Failed to test HTTP challenge for domain ${domain} because HTTP status code ${res.statusCode} was returned: ${parsedBody.message}`);
- resolve(undefined);
- } else {
- resolve(parsedBody);
- }
- } catch (err) {
- if (res.statusCode !== 200) {
- logger.warn(`Failed to test HTTP challenge for domain ${domain} because HTTP status code ${res.statusCode} was returned`);
- } else {
- logger.warn(`Failed to test HTTP challenge for domain ${domain} because response failed to be parsed: ${err.message}`);
- }
- resolve(undefined);
- }
- });
- });
-
- // Make sure to write the request body.
- req.write(formBody);
- req.end();
- req.on('error', function (e) {
- logger.warn(`Failed to test HTTP challenge for domain ${domain}`, e);
- resolve(undefined);
- });
- });
-
- if (!result) {
- // Some error occurred while trying to get the data
- return 'failed';
- } else if (result.error) {
- logger.info(`HTTP challenge test failed for domain ${domain} because error was returned: ${result.error.msg}`);
- return `other:${result.error.msg}`;
- } else if (`${result.responsecode}` === '200' && result.htmlresponse === 'Success') {
- // Server exists and has responded with the correct data
- return 'ok';
- } else if (`${result.responsecode}` === '200') {
- // Server exists but has responded with wrong data
- logger.info(`HTTP challenge test failed for domain ${domain} because of invalid returned data:`, result.htmlresponse);
- return 'wrong-data';
- } else if (`${result.responsecode}` === '404') {
- // Server exists but responded with a 404
- logger.info(`HTTP challenge test failed for domain ${domain} because code 404 was returned`);
- return '404';
- } else if (`${result.responsecode}` === '0' || (typeof result.reason === 'string' && result.reason.toLowerCase() === 'host unavailable')) {
- // Server does not exist at domain
- logger.info(`HTTP challenge test failed for domain ${domain} the host was not found`);
- return 'no-host';
- } else {
- // Other errors
- logger.info(`HTTP challenge test failed for domain ${domain} because code ${result.responsecode} was returned`);
- return `other:${result.responsecode}`;
- }
- }
-
- const results = {};
-
- for (const domain of domains) {
- results[domain] = await performTestForDomain(domain);
- }
-
- // Remove the test challenge file
- fs.unlinkSync(testChallengeFile);
-
- return results;
- },
-};
-
-module.exports = internalCertificate;
diff --git a/backend/internal/dead-host.js b/backend/internal/dead-host.js
deleted file mode 100644
index 0eae1f82f..000000000
--- a/backend/internal/dead-host.js
+++ /dev/null
@@ -1,450 +0,0 @@
-const _ = require('lodash');
-const error = require('../lib/error');
-const utils = require('../lib/utils');
-const deadHostModel = require('../models/dead_host');
-const internalHost = require('./host');
-const internalNginx = require('./nginx');
-const internalAuditLog = require('./audit-log');
-const internalCertificate = require('./certificate');
-
-function omissions() {
- return ['is_deleted'];
-}
-
-const internalDeadHost = {
- /**
- * @param {Access} access
- * @param {Object} data
- * @returns {Promise}
- */
- create: (access, data) => {
- const create_certificate = data.certificate_id === 'new';
-
- if (create_certificate) {
- delete data.certificate_id;
- }
-
- return access
- .can('dead_hosts:create', data)
- .then((/* access_data */) => {
- // Get a list of the domain names and check each of them against existing records
- const domain_name_check_promises = [];
-
- data.domain_names.map(function (domain_name) {
- domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name));
- });
-
- return Promise.all(domain_name_check_promises).then((check_results) => {
- check_results.map(function (result) {
- if (result.is_taken) {
- throw new error.ValidationError(result.hostname + ' is already in use');
- }
- });
- });
- })
- .then(() => {
- // At this point the domains should have been checked
- data.owner_user_id = access.token.getUserId(1);
- data = internalHost.cleanSslHstsData(data);
-
- return deadHostModel.query().insertAndFetch(data).then(utils.omitRow(omissions()));
- })
- .then((row) => {
- if (create_certificate) {
- return internalCertificate
- .createQuickCertificate(access, data)
- .then((cert) => {
- // update host with cert id
- return internalDeadHost.update(access, {
- id: row.id,
- certificate_id: cert.id,
- });
- })
- .then(() => {
- return row;
- });
- } else {
- return row;
- }
- })
- .then((row) => {
- // re-fetch with cert
- return internalDeadHost.get(access, {
- id: row.id,
- expand: ['certificate', 'owner'],
- });
- })
- .then((row) => {
- // Configure nginx
- return internalNginx.configure(deadHostModel, 'dead_host', row).then(() => {
- return row;
- });
- })
- .then((row) => {
- data.meta = _.assign({}, data.meta || {}, row.meta);
-
- // Add to audit log
- return internalAuditLog
- .add(access, {
- action: 'created',
- object_type: 'dead-host',
- object_id: row.id,
- meta: data,
- })
- .then(() => {
- return row;
- });
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @return {Promise}
- */
- update: (access, data) => {
- const create_certificate = data.certificate_id === 'new';
-
- if (create_certificate) {
- delete data.certificate_id;
- }
-
- return access
- .can('dead_hosts:update', data.id)
- .then((/* access_data */) => {
- // Get a list of the domain names and check each of them against existing records
- const domain_name_check_promises = [];
-
- if (typeof data.domain_names !== 'undefined') {
- data.domain_names.map(function (domain_name) {
- domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'dead', data.id));
- });
-
- return Promise.all(domain_name_check_promises).then((check_results) => {
- check_results.map(function (result) {
- if (result.is_taken) {
- throw new error.ValidationError(result.hostname + ' is already in use');
- }
- });
- });
- }
- })
- .then(() => {
- return internalDeadHost.get(access, { id: data.id });
- })
- .then((row) => {
- if (row.id !== data.id) {
- // Sanity check that something crazy hasn't happened
- throw new error.InternalValidationError('404 Host could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
- }
-
- if (create_certificate) {
- return internalCertificate
- .createQuickCertificate(access, {
- domain_names: data.domain_names || row.domain_names,
- meta: _.assign({}, row.meta, data.meta),
- })
- .then((cert) => {
- // update host with cert id
- data.certificate_id = cert.id;
- })
- .then(() => {
- return row;
- });
- } else {
- return row;
- }
- })
- .then((row) => {
- // Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here.
- data = _.assign(
- {},
- {
- domain_names: row.domain_names,
- },
- data,
- );
-
- data = internalHost.cleanSslHstsData(data, row);
-
- return deadHostModel
- .query()
- .where({ id: data.id })
- .patch(data)
- .then((saved_row) => {
- // Add to audit log
- return internalAuditLog
- .add(access, {
- action: 'updated',
- object_type: 'dead-host',
- object_id: row.id,
- meta: data,
- })
- .then(() => {
- return _.omit(saved_row, omissions());
- });
- });
- })
- .then(() => {
- return internalDeadHost
- .get(access, {
- id: data.id,
- expand: ['owner', 'certificate'],
- })
- .then((row) => {
- // Configure nginx
- return internalNginx.configure(deadHostModel, 'dead_host', row).then((new_meta) => {
- row.meta = new_meta;
- row = internalHost.cleanRowCertificateMeta(row);
- return _.omit(row, omissions());
- });
- });
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @param {Array} [data.expand]
- * @param {Array} [data.omit]
- * @return {Promise}
- */
- get: (access, data) => {
- if (typeof data === 'undefined') {
- data = {};
- }
-
- return access
- .can('dead_hosts:get', data.id)
- .then((access_data) => {
- const query = deadHostModel.query().where('is_deleted', 0).andWhere('id', data.id).allowGraph('[owner,certificate]').first();
-
- if (access_data.permission_visibility !== 'all') {
- query.andWhere('owner_user_id', access.token.getUserId(1));
- }
-
- if (typeof data.expand !== 'undefined' && data.expand !== null) {
- query.withGraphFetched('[' + data.expand.join(', ') + ']');
- }
-
- return query.then(utils.omitRow(omissions()));
- })
- .then((row) => {
- if (!row) {
- throw new error.ItemNotFoundError(data.id);
- }
- // Custom omissions
- if (typeof data.omit !== 'undefined' && data.omit !== null) {
- row = _.omit(row, data.omit);
- }
- return row;
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @param {String} [data.reason]
- * @returns {Promise}
- */
- delete: (access, data) => {
- return access
- .can('dead_hosts:delete', data.id)
- .then(() => {
- return internalDeadHost.get(access, { id: data.id });
- })
- .then((row) => {
- if (!row) {
- throw new error.ItemNotFoundError(data.id);
- }
-
- return deadHostModel
- .query()
- .where('id', row.id)
- .patch({
- is_deleted: 1,
- })
- .then(() => {
- // Delete Nginx Config
- return internalNginx.deleteConfig('dead_host', row).then(() => {
- return internalNginx.reload();
- });
- })
- .then(() => {
- // Add to audit log
- return internalAuditLog.add(access, {
- action: 'deleted',
- object_type: 'dead-host',
- object_id: row.id,
- meta: _.omit(row, omissions()),
- });
- });
- })
- .then(() => {
- return true;
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @param {String} [data.reason]
- * @returns {Promise}
- */
- enable: (access, data) => {
- return access
- .can('dead_hosts:update', data.id)
- .then(() => {
- return internalDeadHost.get(access, {
- id: data.id,
- expand: ['certificate', 'owner'],
- });
- })
- .then((row) => {
- if (!row) {
- throw new error.ItemNotFoundError(data.id);
- } else if (row.enabled) {
- throw new error.ValidationError('Host is already enabled');
- }
-
- row.enabled = 1;
-
- return deadHostModel
- .query()
- .where('id', row.id)
- .patch({
- enabled: 1,
- })
- .then(() => {
- // Configure nginx
- return internalNginx.configure(deadHostModel, 'dead_host', row);
- })
- .then(() => {
- // Add to audit log
- return internalAuditLog.add(access, {
- action: 'enabled',
- object_type: 'dead-host',
- object_id: row.id,
- meta: _.omit(row, omissions()),
- });
- });
- })
- .then(() => {
- return true;
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @param {String} [data.reason]
- * @returns {Promise}
- */
- disable: (access, data) => {
- return access
- .can('dead_hosts:update', data.id)
- .then(() => {
- return internalDeadHost.get(access, { id: data.id });
- })
- .then((row) => {
- if (!row) {
- throw new error.ItemNotFoundError(data.id);
- } else if (!row.enabled) {
- throw new error.ValidationError('Host is already disabled');
- }
-
- row.enabled = 0;
-
- return deadHostModel
- .query()
- .where('id', row.id)
- .patch({
- enabled: 0,
- })
- .then(() => {
- // Delete Nginx Config
- return internalNginx.deleteConfig('dead_host', row).then(() => {
- return internalNginx.reload();
- });
- })
- .then(() => {
- // Add to audit log
- return internalAuditLog.add(access, {
- action: 'disabled',
- object_type: 'dead-host',
- object_id: row.id,
- meta: _.omit(row, omissions()),
- });
- });
- })
- .then(() => {
- return true;
- });
- },
-
- /**
- * All Hosts
- *
- * @param {Access} access
- * @param {Array} [expand]
- * @param {String} [search_query]
- * @returns {Promise}
- */
- getAll: (access, expand, search_query) => {
- return access
- .can('dead_hosts:list')
- .then((access_data) => {
- const query = deadHostModel.query().where('is_deleted', 0).groupBy('id').allowGraph('[owner,certificate]').orderBy('domain_names', 'ASC');
-
- if (access_data.permission_visibility !== 'all') {
- query.andWhere('owner_user_id', access.token.getUserId(1));
- }
-
- // Query is used for searching
- if (typeof search_query === 'string') {
- query.where(function () {
- this.where('domain_names', 'like', '%' + search_query + '%');
- });
- }
-
- if (typeof expand !== 'undefined' && expand !== null) {
- query.withGraphFetched('[' + expand.join(', ') + ']');
- }
-
- return query.then(utils.omitRows(omissions()));
- })
- .then((rows) => {
- if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
- return internalHost.cleanAllRowsCertificateMeta(rows);
- }
-
- return rows;
- });
- },
-
- /**
- * Report use
- *
- * @param {Number} user_id
- * @param {String} visibility
- * @returns {Promise}
- */
- getCount: (user_id, visibility) => {
- const query = deadHostModel.query().count('id as count').where('is_deleted', 0);
-
- if (visibility !== 'all') {
- query.andWhere('owner_user_id', user_id);
- }
-
- return query.first().then((row) => {
- return parseInt(row.count, 10);
- });
- },
-};
-
-module.exports = internalDeadHost;
diff --git a/backend/internal/host.js b/backend/internal/host.js
deleted file mode 100644
index b858955bb..000000000
--- a/backend/internal/host.js
+++ /dev/null
@@ -1,211 +0,0 @@
-const _ = require('lodash');
-const proxyHostModel = require('../models/proxy_host');
-const redirectionHostModel = require('../models/redirection_host');
-const deadHostModel = require('../models/dead_host');
-
-const internalHost = {
- /**
- * Makes sure that the ssl_* and hsts_* fields play nicely together.
- * ie: if force_ssl is off, then hsts_enabled is definitely off.
- *
- * @param {object} data
- * @param {object} [existing_data]
- * @returns {object}
- */
- cleanSslHstsData: function (data, existing_data) {
- existing_data = existing_data === undefined ? {} : existing_data;
-
- const combined_data = _.assign({}, existing_data, data);
-
- if (!combined_data.ssl_forced) {
- combined_data.hsts_enabled = false;
- }
-
- return combined_data;
- },
-
- /**
- * used by the getAll functions of hosts, this removes the certificate meta if present
- *
- * @param {Array} rows
- * @returns {Array}
- */
- cleanAllRowsCertificateMeta: function (rows) {
- rows.map(function (row, idx) {
- if (typeof rows[idx].certificate !== 'undefined' && rows[idx].certificate) {
- rows[idx].certificate.meta = {};
- }
- });
-
- return rows;
- },
-
- /**
- * used by the get/update functions of hosts, this removes the certificate meta if present
- *
- * @param {Object} row
- * @returns {Object}
- */
- cleanRowCertificateMeta: function (row) {
- if (typeof row.certificate !== 'undefined' && row.certificate) {
- row.certificate.meta = {};
- }
-
- return row;
- },
-
- /**
- * This returns all the host types with any domain listed in the provided domain_names array.
- * This is used by the certificates to temporarily disable any host that is using the domain
- *
- * @param {Array} domain_names
- * @returns {Promise}
- */
- getHostsWithDomains: function (domain_names) {
- const promises = [proxyHostModel.query().where('is_deleted', 0), redirectionHostModel.query().where('is_deleted', 0), deadHostModel.query().where('is_deleted', 0)];
-
- return Promise.all(promises).then((promises_results) => {
- const response_object = {
- total_count: 0,
- dead_hosts: [],
- proxy_hosts: [],
- redirection_hosts: [],
- };
-
- if (promises_results[0]) {
- // Proxy Hosts
- response_object.proxy_hosts = internalHost._getHostsWithDomains(promises_results[0], domain_names);
- response_object.total_count += response_object.proxy_hosts.length;
- }
-
- if (promises_results[1]) {
- // Redirection Hosts
- response_object.redirection_hosts = internalHost._getHostsWithDomains(promises_results[1], domain_names);
- response_object.total_count += response_object.redirection_hosts.length;
- }
-
- if (promises_results[2]) {
- // Dead Hosts
- response_object.dead_hosts = internalHost._getHostsWithDomains(promises_results[2], domain_names);
- response_object.total_count += response_object.dead_hosts.length;
- }
-
- return response_object;
- });
- },
-
- /**
- * Internal use only, checks to see if the domain is already taken by any other record
- *
- * @param {String} hostname
- * @param {String} [ignore_type] 'proxy', 'redirection', 'dead'
- * @param {Integer} [ignore_id] Must be supplied if type was also supplied
- * @returns {Promise}
- */
- isHostnameTaken: function (hostname, ignore_type, ignore_id) {
- const promises = [
- proxyHostModel
- .query()
- .where('is_deleted', 0)
- .andWhere('domain_names', 'like', '%' + hostname + '%'),
- redirectionHostModel
- .query()
- .where('is_deleted', 0)
- .andWhere('domain_names', 'like', '%' + hostname + '%'),
- deadHostModel
- .query()
- .where('is_deleted', 0)
- .andWhere('domain_names', 'like', '%' + hostname + '%'),
- ];
-
- return Promise.all(promises).then((promises_results) => {
- let is_taken = false;
-
- if (promises_results[0]) {
- // Proxy Hosts
- if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[0], ignore_type === 'proxy' && ignore_id ? ignore_id : 0)) {
- is_taken = true;
- }
- }
-
- if (promises_results[1]) {
- // Redirection Hosts
- if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[1], ignore_type === 'redirection' && ignore_id ? ignore_id : 0)) {
- is_taken = true;
- }
- }
-
- if (promises_results[2]) {
- // Dead Hosts
- if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[2], ignore_type === 'dead' && ignore_id ? ignore_id : 0)) {
- is_taken = true;
- }
- }
-
- return {
- hostname,
- is_taken,
- };
- });
- },
-
- /**
- * Private call only
- *
- * @param {String} hostname
- * @param {Array} existing_rows
- * @param {Integer} [ignore_id]
- * @returns {Boolean}
- */
- _checkHostnameRecordsTaken: function (hostname, existing_rows, ignore_id) {
- let is_taken = false;
-
- if (existing_rows && existing_rows.length) {
- existing_rows.map(function (existing_row) {
- existing_row.domain_names.map(function (existing_hostname) {
- // Does this domain match?
- if (existing_hostname.toLowerCase() === hostname.toLowerCase()) {
- if (!ignore_id || ignore_id !== existing_row.id) {
- is_taken = true;
- }
- }
- });
- });
- }
-
- return is_taken;
- },
-
- /**
- * Private call only
- *
- * @param {Array} hosts
- * @param {Array} domain_names
- * @returns {Array}
- */
- _getHostsWithDomains: function (hosts, domain_names) {
- const response = [];
-
- if (hosts && hosts.length) {
- hosts.map(function (host) {
- let host_matches = false;
-
- domain_names.map(function (domain_name) {
- host.domain_names.map(function (host_domain_name) {
- if (domain_name.toLowerCase() === host_domain_name.toLowerCase()) {
- host_matches = true;
- }
- });
- });
-
- if (host_matches) {
- response.push(host);
- }
- });
- }
-
- return response;
- },
-};
-
-module.exports = internalHost;
diff --git a/backend/internal/ip_ranges.js b/backend/internal/ip_ranges.js
deleted file mode 100644
index d21beb40f..000000000
--- a/backend/internal/ip_ranges.js
+++ /dev/null
@@ -1,127 +0,0 @@
-const https = require('https');
-const fs = require('fs');
-const logger = require('../logger').ip_ranges;
-const error = require('../lib/error');
-const utils = require('../lib/utils');
-const internalNginx = require('./nginx');
-
-const CLOUDFARE_V4_URL = 'https://www.cloudflare.com/ips-v4';
-const CLOUDFARE_V6_URL = 'https://www.cloudflare.com/ips-v6';
-
-const regIpV4 = /^(\d+\.?){4}\/\d+/;
-const regIpV6 = /^(([\da-fA-F]+)?:)+\/\d+/;
-
-const internalIpRanges = {
- interval_timeout: 1000 * 60 * 60 * Number(process.env.IPRT),
- interval: null,
- interval_processing: false,
- iteration_count: 0,
-
- initTimer: () => {
- if (process.env.SKIP_IP_RANGES === 'false') {
- logger.info('IP Ranges Renewal Timer initialized');
- internalIpRanges.interval = setInterval(internalIpRanges.fetch, internalIpRanges.interval_timeout);
- }
- },
-
- fetchUrl: (url) => {
- return new Promise((resolve, reject) => {
- logger.info('Fetching ' + url);
- return https
- .get(url, (res) => {
- res.setEncoding('utf8');
- let raw_data = '';
- res.on('data', (chunk) => {
- raw_data += chunk;
- });
-
- res.on('end', () => {
- resolve(raw_data);
- });
- })
- .on('error', (err) => {
- reject(err);
- });
- });
- },
-
- /**
- * Triggered at startup and then later by a timer, this will fetch the ip ranges from services and apply them to nginx.
- */
- fetch: () => {
- if (!internalIpRanges.interval_processing && process.env.SKIP_IP_RANGES === 'false') {
- internalIpRanges.interval_processing = true;
- logger.info('Fetching IP Ranges from online services...');
-
- let ip_ranges = [];
-
- return internalIpRanges
- .fetchUrl(CLOUDFARE_V4_URL)
- .then((cloudfare_data) => {
- const items = cloudfare_data.split('\n').filter((line) => regIpV4.test(line));
- ip_ranges = [...ip_ranges, ...items];
- })
- .then(() => {
- return internalIpRanges.fetchUrl(CLOUDFARE_V6_URL);
- })
- .then((cloudfare_data) => {
- const items = cloudfare_data.split('\n').filter((line) => regIpV6.test(line));
- ip_ranges = [...ip_ranges, ...items];
- })
- .then(() => {
- const clean_ip_ranges = [];
- ip_ranges.map((range) => {
- if (range) {
- clean_ip_ranges.push(range);
- }
- });
-
- return internalIpRanges.generateConfig(clean_ip_ranges).then(() => {
- if (internalIpRanges.iteration_count) {
- // Reload nginx
- return internalNginx.reload();
- }
- });
- })
- .then(() => {
- internalIpRanges.interval_processing = false;
- internalIpRanges.iteration_count++;
- })
- .catch((err) => {
- logger.error(err.message);
- internalIpRanges.interval_processing = false;
- });
- }
- },
-
- /**
- * @param {Array} ip_ranges
- * @returns {Promise}
- */
- generateConfig: (ip_ranges) => {
- const renderEngine = utils.getRenderEngine();
- return new Promise((resolve, reject) => {
- let template = null;
- const filename = '/tmp/ip_ranges.conf';
- try {
- template = fs.readFileSync(__dirname + '/../templates/ip_ranges.conf', { encoding: 'utf8' });
- } catch (err) {
- reject(new error.ConfigurationError(err.message));
- return;
- }
-
- renderEngine
- .parseAndRender(template, { ip_ranges })
- .then((config_text) => {
- fs.writeFileSync(filename, config_text, { encoding: 'utf8' });
- resolve(true);
- })
- .catch((err) => {
- logger.warn('Could not write ' + filename + ':', err.message);
- reject(new error.ConfigurationError(err.message));
- });
- });
- },
-};
-
-module.exports = internalIpRanges;
diff --git a/backend/internal/nginx.js b/backend/internal/nginx.js
deleted file mode 100644
index 76844851b..000000000
--- a/backend/internal/nginx.js
+++ /dev/null
@@ -1,346 +0,0 @@
-const _ = require('lodash');
-const fs = require('fs');
-const logger = require('../logger').nginx;
-const config = require('../lib/config');
-const utils = require('../lib/utils');
-const error = require('../lib/error');
-
-const NgxPidFilePath = '/usr/local/nginx/logs/nginx.pid';
-
-const internalNginx = {
- /**
- * This will:
- * - test the nginx config first to make sure it's OK
- * - create / recreate the config for the host
- * - test again
- * - IF OK: update the meta with online status
- * - IF BAD: update the meta with offline status and remove the config entirely
- * - then reload nginx
- *
- * @param {Object|String} model
- * @param {String} host_type
- * @param {Object} host
- * @returns {Promise}
- */
- configure: (model, host_type, host) => {
- let combined_meta = {};
-
- return internalNginx
- .test()
- .then(() => {
- // Nginx is OK
- // We're deleting this config regardless.
- return internalNginx.deleteConfig(host_type, host);
- })
- .then(() => {
- return internalNginx.generateConfig(host_type, host);
- })
- .then(() => {
- // Test nginx again and update meta with result
- return internalNginx
- .test()
- .then(() => {
- // Nginx is OK
- combined_meta = _.assign({}, host.meta, {
- nginx_online: true,
- nginx_err: null,
- });
-
- return model.query().where('id', host.id).patch({
- meta: combined_meta,
- });
- })
- .catch(() => {
- // Handle testing failure
- // Execute the command and wait for it to finish
- return utils.execfg('nginx -t || true').then(() => {
- combined_meta = _.assign({}, host.meta, {
- nginx_online: false,
- nginx_err: 'see docker logs',
- });
-
- return model
- .query()
- .where('id', host.id)
- .patch({
- meta: combined_meta,
- })
- .then(() => {
- internalNginx.renameConfigAsError(host_type, host);
- });
- });
- });
- })
- .then(() => {
- return internalNginx.reload();
- })
- .then(() => {
- return combined_meta;
- });
- },
-
- /**
- * @returns {Promise}
- */
- test: () => {
- if (config.debug()) {
- logger.info('Testing Nginx configuration');
- }
-
- return utils.exec('nginx -tq');
- },
-
- /**
- * @returns {Promise}
- */
-
- reload: () => {
- return internalNginx.test().then(() => {
- try {
- utils.exec('certbot-ocsp-fetcher.sh -c /data/tls/certbot -o /data/tls/certbot/live --quiet --no-reload-webserver || true');
- } catch {
- // do nothing
- }
- if (fs.existsSync(NgxPidFilePath)) {
- const ngxPID = fs.readFileSync(NgxPidFilePath, 'utf8').trim();
- if (ngxPID.length > 0) {
- logger.info('Reloading Nginx');
- utils.exec('nginx -s reload');
- } else {
- logger.info('Starting Nginx');
- utils.execfg('nginx -e stderr');
- }
- } else {
- logger.info('Starting Nginx');
- utils.execfg('nginx -e stderr');
- }
- });
- },
-
- /**
- * @param {String} host_type
- * @param {Integer} host_id
- * @returns {String}
- */
- getConfigName: (host_type, host_id) => {
- if (host_type === 'default') {
- return '/data/nginx/default.conf';
- }
- return '/data/nginx/' + internalNginx.getFileFriendlyHostType(host_type) + '/' + host_id + '.conf';
- },
-
- /**
- * Generates custom locations
- * @param {Object} host
- * @returns {Promise}
- */
- renderLocations: (host) => {
- return new Promise((resolve, reject) => {
- let template;
-
- try {
- template = fs.readFileSync(__dirname + '/../templates/_location.conf', { encoding: 'utf8' });
- } catch (err) {
- reject(new error.ConfigurationError(err.message));
- return;
- }
-
- const renderEngine = utils.getRenderEngine();
- let renderedLocations = '';
-
- const locationRendering = async () => {
- for (let i = 0; i < host.locations.length; i++) {
- const locationCopy = Object.assign({}, { access_list_id: host.access_list_id }, { certificate_id: host.certificate_id }, { ssl_forced: host.ssl_forced }, { caching_enabled: host.caching_enabled }, { block_exploits: host.block_exploits }, { allow_websocket_upgrade: host.allow_websocket_upgrade }, { http2_support: host.http2_support }, { hsts_enabled: host.hsts_enabled }, { hsts_subdomains: host.hsts_subdomains }, { access_list: host.access_list }, { certificate: host.certificate }, host.locations[i]);
-
- if (locationCopy.forward_host.indexOf('/') > -1) {
- const split = locationCopy.forward_host.split('/');
-
- locationCopy.forward_host = split.shift();
- locationCopy.forward_path = `/${split.join('/')}`;
- }
-
- renderedLocations += await renderEngine.parseAndRender(template, locationCopy);
- }
- };
-
- locationRendering().then(() => resolve(renderedLocations));
- });
- },
-
- /**
- * @param {String} host_type
- * @param {Object} host
- * @returns {Promise}
- */
- generateConfig: (host_type, host) => {
- const nice_host_type = internalNginx.getFileFriendlyHostType(host_type);
-
- if (config.debug()) {
- logger.info('Generating ' + nice_host_type + ' Config:', JSON.stringify(host, null, 2));
- }
-
- const renderEngine = utils.getRenderEngine();
-
- return new Promise((resolve, reject) => {
- let template = null;
- const filename = internalNginx.getConfigName(nice_host_type, host.id);
-
- try {
- template = fs.readFileSync(__dirname + '/../templates/' + nice_host_type + '.conf', { encoding: 'utf8' });
- } catch (err) {
- reject(new error.ConfigurationError(err.message));
- return;
- }
-
- let locationsPromise;
- let origLocations;
-
- // Manipulate the data a bit before sending it to the template
- if (nice_host_type !== 'default') {
- host.use_default_location = true;
- if (typeof host.advanced_config !== 'undefined' && host.advanced_config) {
- host.use_default_location = !internalNginx.advancedConfigHasDefaultLocation(host.advanced_config);
- }
- }
-
- if (host.locations) {
- // logger.info ('host.locations = ' + JSON.stringify(host.locations, null, 2));
- origLocations = [].concat(host.locations);
- locationsPromise = internalNginx.renderLocations(host).then((renderedLocations) => {
- host.locations = renderedLocations;
- });
-
- // Allow someone who is using / custom location path to use it, and skip the default / location
- _.map(host.locations, (location) => {
- if (location.path === '/') {
- host.use_default_location = false;
- }
- });
- } else {
- locationsPromise = Promise.resolve();
- }
-
- // Set the IPv6 setting for the host
- host.ipv6 = internalNginx.ipv6Enabled();
-
- locationsPromise.then(() => {
- renderEngine
- .parseAndRender(template, host)
- .then((config_text) => {
- fs.writeFileSync(filename, config_text, { encoding: 'utf8' });
-
- if (config.debug()) {
- logger.success('Wrote config:', filename, config_text);
- }
-
- // Restore locations array
- host.locations = origLocations;
-
- resolve(true);
- })
- .catch((err) => {
- if (config.debug()) {
- logger.warn('Could not write ' + filename + ':', err.message);
- }
-
- reject(new error.ConfigurationError(err.message));
- });
- });
- });
- },
-
- /**
- *
- * @param {String} host_type
- * @returns String
- */
- getFileFriendlyHostType: (host_type) => {
- return host_type.replace(new RegExp('-', 'g'), '_');
- },
-
- /**
- * @param {String} host_type
- * @param {Object} [host]
- * @param {Boolean} [delete_err_file]
- * @returns {Promise}
- */
- deleteConfig: (host_type, host) => {
- const config_file = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id);
- const config_file_err = config_file + '.err';
-
- return new Promise((resolve /*, reject */) => {
- fs.rm(config_file, { force: true }, () => {
- resolve();
- });
- fs.rm(config_file_err, { force: true }, () => {
- resolve();
- });
- });
- },
-
- /**
- * @param {String} host_type
- * @param {Object} [host]
- * @returns {Promise}
- */
- renameConfigAsError: (host_type, host) => {
- const config_file = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id);
- const config_file_err = config_file + '.err';
-
- return new Promise((resolve /*, reject */) => {
- fs.rename(config_file, config_file_err, () => {
- resolve();
- });
- });
- },
-
- /**
- * @param {String} host_type
- * @param {Array} hosts
- * @returns {Promise}
- */
- bulkGenerateConfigs: (host_type, hosts) => {
- const promises = [];
- hosts.map(function (host) {
- promises.push(internalNginx.generateConfig(host_type, host));
- });
-
- return Promise.all(promises);
- },
-
- /**
- * @param {String} host_type
- * @param {Array} hosts
- * @returns {Promise}
- */
- bulkDeleteConfigs: (host_type, hosts) => {
- const promises = [];
- hosts.map(function (host) {
- promises.push(internalNginx.deleteConfig(host_type, host));
- });
-
- return Promise.all(promises);
- },
-
- /**
- * @param {string} config
- * @returns {boolean}
- */
- advancedConfigHasDefaultLocation: function (cfg) {
- return !!cfg.match(/^(?:.*;)?\s*?location\s*?\/\s*?{/im);
- },
-
- /**
- * @returns {boolean}
- */
- ipv6Enabled: function () {
- if (typeof process.env.DISABLE_IPV6 !== 'undefined') {
- const disabled = process.env.DISABLE_IPV6.toLowerCase();
- return !(disabled === 'on' || disabled === 'true' || disabled === '1' || disabled === 'yes');
- }
-
- return true;
- },
-};
-
-module.exports = internalNginx;
diff --git a/backend/internal/proxy-host.js b/backend/internal/proxy-host.js
deleted file mode 100644
index f496b06ce..000000000
--- a/backend/internal/proxy-host.js
+++ /dev/null
@@ -1,457 +0,0 @@
-const _ = require('lodash');
-const error = require('../lib/error');
-const utils = require('../lib/utils');
-const proxyHostModel = require('../models/proxy_host');
-const internalHost = require('./host');
-const internalNginx = require('./nginx');
-const internalAuditLog = require('./audit-log');
-const internalCertificate = require('./certificate');
-
-function omissions() {
- return ['is_deleted'];
-}
-
-const internalProxyHost = {
- /**
- * @param {Access} access
- * @param {Object} data
- * @returns {Promise}
- */
- create: (access, data) => {
- const create_certificate = data.certificate_id === 'new';
-
- if (create_certificate) {
- delete data.certificate_id;
- }
-
- return access
- .can('proxy_hosts:create', data)
- .then(() => {
- // Get a list of the domain names and check each of them against existing records
- const domain_name_check_promises = [];
-
- data.domain_names.map(function (domain_name) {
- domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name));
- });
-
- return Promise.all(domain_name_check_promises).then((check_results) => {
- check_results.map(function (result) {
- if (result.is_taken) {
- throw new error.ValidationError(result.hostname + ' is already in use');
- }
- });
- });
- })
- .then(() => {
- // At this point the domains should have been checked
- data.owner_user_id = access.token.getUserId(1);
- data = internalHost.cleanSslHstsData(data);
-
- return proxyHostModel.query().insertAndFetch(data).then(utils.omitRow(omissions()));
- })
- .then((row) => {
- if (create_certificate) {
- return internalCertificate
- .createQuickCertificate(access, data)
- .then((cert) => {
- // update host with cert id
- return internalProxyHost.update(access, {
- id: row.id,
- certificate_id: cert.id,
- });
- })
- .then(() => {
- return row;
- });
- } else {
- return row;
- }
- })
- .then((row) => {
- // re-fetch with cert
- return internalProxyHost.get(access, {
- id: row.id,
- expand: ['certificate', 'owner', 'access_list.[clients,items]'],
- });
- })
- .then((row) => {
- // Configure nginx
- return internalNginx.configure(proxyHostModel, 'proxy_host', row).then(() => {
- return row;
- });
- })
- .then((row) => {
- // Audit log
- data.meta = _.assign({}, data.meta || {}, row.meta);
-
- // Add to audit log
- return internalAuditLog
- .add(access, {
- action: 'created',
- object_type: 'proxy-host',
- object_id: row.id,
- meta: data,
- })
- .then(() => {
- return row;
- });
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @return {Promise}
- */
- update: (access, data) => {
- const create_certificate = data.certificate_id === 'new';
-
- if (create_certificate) {
- delete data.certificate_id;
- }
-
- return access
- .can('proxy_hosts:update', data.id)
- .then((/* access_data */) => {
- // Get a list of the domain names and check each of them against existing records
- const domain_name_check_promises = [];
-
- if (typeof data.domain_names !== 'undefined') {
- data.domain_names.map(function (domain_name) {
- domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'proxy', data.id));
- });
-
- return Promise.all(domain_name_check_promises).then((check_results) => {
- check_results.map(function (result) {
- if (result.is_taken) {
- throw new error.ValidationError(result.hostname + ' is already in use');
- }
- });
- });
- }
- })
- .then(() => {
- return internalProxyHost.get(access, { id: data.id });
- })
- .then((row) => {
- if (row.id !== data.id) {
- // Sanity check that something crazy hasn't happened
- throw new error.InternalValidationError('Proxy Host could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
- }
-
- if (create_certificate) {
- return internalCertificate
- .createQuickCertificate(access, {
- domain_names: data.domain_names || row.domain_names,
- meta: _.assign({}, row.meta, data.meta),
- })
- .then((cert) => {
- // update host with cert id
- data.certificate_id = cert.id;
- })
- .then(() => {
- return row;
- });
- } else {
- return row;
- }
- })
- .then((row) => {
- // Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here.
- data = _.assign(
- {},
- {
- domain_names: row.domain_names,
- },
- data,
- );
-
- data = internalHost.cleanSslHstsData(data, row);
-
- return proxyHostModel
- .query()
- .where({ id: data.id })
- .patch(data)
- .then(utils.omitRow(omissions()))
- .then((saved_row) => {
- // Add to audit log
- return internalAuditLog
- .add(access, {
- action: 'updated',
- object_type: 'proxy-host',
- object_id: row.id,
- meta: data,
- })
- .then(() => {
- return saved_row;
- });
- });
- })
- .then(() => {
- return internalProxyHost
- .get(access, {
- id: data.id,
- expand: ['owner', 'certificate', 'access_list.[clients,items]'],
- })
- .then((row) => {
- if (!row.enabled) {
- // No need to add nginx config if host is disabled
- return row;
- }
- // Configure nginx
- return internalNginx.configure(proxyHostModel, 'proxy_host', row).then((new_meta) => {
- row.meta = new_meta;
- row = internalHost.cleanRowCertificateMeta(row);
- return _.omit(row, omissions());
- });
- });
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @param {Array} [data.expand]
- * @param {Array} [data.omit]
- * @return {Promise}
- */
- get: (access, data) => {
- if (typeof data === 'undefined') {
- data = {};
- }
-
- return access
- .can('proxy_hosts:get', data.id)
- .then((access_data) => {
- const query = proxyHostModel.query().where('is_deleted', 0).andWhere('id', data.id).allowGraph('[owner,access_list.[clients,items],certificate]').first();
-
- if (access_data.permission_visibility !== 'all') {
- query.andWhere('owner_user_id', access.token.getUserId(1));
- }
-
- if (typeof data.expand !== 'undefined' && data.expand !== null) {
- query.withGraphFetched('[' + data.expand.join(', ') + ']');
- }
-
- return query.then(utils.omitRow(omissions()));
- })
- .then((row) => {
- if (!row) {
- throw new error.ItemNotFoundError(data.id);
- }
- row = internalHost.cleanRowCertificateMeta(row);
- // Custom omissions
- if (typeof data.omit !== 'undefined' && data.omit !== null) {
- row = _.omit(row, data.omit);
- }
- return row;
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @param {String} [data.reason]
- * @returns {Promise}
- */
- delete: (access, data) => {
- return access
- .can('proxy_hosts:delete', data.id)
- .then(() => {
- return internalProxyHost.get(access, { id: data.id });
- })
- .then((row) => {
- if (!row) {
- throw new error.ItemNotFoundError(data.id);
- }
-
- return proxyHostModel
- .query()
- .where('id', row.id)
- .patch({
- is_deleted: 1,
- })
- .then(() => {
- // Delete Nginx Config
- return internalNginx.deleteConfig('proxy_host', row).then(() => {
- return internalNginx.reload();
- });
- })
- .then(() => {
- // Add to audit log
- return internalAuditLog.add(access, {
- action: 'deleted',
- object_type: 'proxy-host',
- object_id: row.id,
- meta: _.omit(row, omissions()),
- });
- });
- })
- .then(() => {
- return true;
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @param {String} [data.reason]
- * @returns {Promise}
- */
- enable: (access, data) => {
- return access
- .can('proxy_hosts:update', data.id)
- .then(() => {
- return internalProxyHost.get(access, {
- id: data.id,
- expand: ['certificate', 'owner', 'access_list'],
- });
- })
- .then((row) => {
- if (!row) {
- throw new error.ItemNotFoundError(data.id);
- } else if (row.enabled) {
- throw new error.ValidationError('Host is already enabled');
- }
-
- row.enabled = 1;
-
- return proxyHostModel
- .query()
- .where('id', row.id)
- .patch({
- enabled: 1,
- })
- .then(() => {
- // Configure nginx
- return internalNginx.configure(proxyHostModel, 'proxy_host', row);
- })
- .then(() => {
- // Add to audit log
- return internalAuditLog.add(access, {
- action: 'enabled',
- object_type: 'proxy-host',
- object_id: row.id,
- meta: _.omit(row, omissions()),
- });
- });
- })
- .then(() => {
- return true;
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @param {String} [data.reason]
- * @returns {Promise}
- */
- disable: (access, data) => {
- return access
- .can('proxy_hosts:update', data.id)
- .then(() => {
- return internalProxyHost.get(access, { id: data.id });
- })
- .then((row) => {
- if (!row) {
- throw new error.ItemNotFoundError(data.id);
- } else if (!row.enabled) {
- throw new error.ValidationError('Host is already disabled');
- }
-
- row.enabled = 0;
-
- return proxyHostModel
- .query()
- .where('id', row.id)
- .patch({
- enabled: 0,
- })
- .then(() => {
- // Delete Nginx Config
- return internalNginx.deleteConfig('proxy_host', row).then(() => {
- return internalNginx.reload();
- });
- })
- .then(() => {
- // Add to audit log
- return internalAuditLog.add(access, {
- action: 'disabled',
- object_type: 'proxy-host',
- object_id: row.id,
- meta: _.omit(row, omissions()),
- });
- });
- })
- .then(() => {
- return true;
- });
- },
-
- /**
- * All Hosts
- *
- * @param {Access} access
- * @param {Array} [expand]
- * @param {String} [search_query]
- * @returns {Promise}
- */
- getAll: (access, expand, search_query) => {
- return access
- .can('proxy_hosts:list')
- .then((access_data) => {
- const query = proxyHostModel.query().where('is_deleted', 0).groupBy('id').allowGraph('[owner,access_list,certificate]').orderBy('domain_names', 'ASC');
-
- if (access_data.permission_visibility !== 'all') {
- query.andWhere('owner_user_id', access.token.getUserId(1));
- }
-
- // Query is used for searching
- if (typeof search_query === 'string') {
- query.where(function () {
- this.where('domain_names', 'like', '%' + search_query + '%');
- });
- }
-
- if (typeof expand !== 'undefined' && expand !== null) {
- query.withGraphFetched('[' + expand.join(', ') + ']');
- }
-
- return query.then(utils.omitRows(omissions()));
- })
- .then((rows) => {
- if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
- return internalHost.cleanAllRowsCertificateMeta(rows);
- }
-
- return rows;
- });
- },
-
- /**
- * Report use
- *
- * @param {Number} user_id
- * @param {String} visibility
- * @returns {Promise}
- */
- getCount: (user_id, visibility) => {
- const query = proxyHostModel.query().count('id as count').where('is_deleted', 0);
-
- if (visibility !== 'all') {
- query.andWhere('owner_user_id', user_id);
- }
-
- return query.first().then((row) => {
- return parseInt(row.count, 10);
- });
- },
-};
-
-module.exports = internalProxyHost;
diff --git a/backend/internal/redirection-host.js b/backend/internal/redirection-host.js
deleted file mode 100644
index 971fc0e53..000000000
--- a/backend/internal/redirection-host.js
+++ /dev/null
@@ -1,450 +0,0 @@
-const _ = require('lodash');
-const error = require('../lib/error');
-const utils = require('../lib/utils');
-const redirectionHostModel = require('../models/redirection_host');
-const internalHost = require('./host');
-const internalNginx = require('./nginx');
-const internalAuditLog = require('./audit-log');
-const internalCertificate = require('./certificate');
-
-function omissions() {
- return ['is_deleted'];
-}
-
-const internalRedirectionHost = {
- /**
- * @param {Access} access
- * @param {Object} data
- * @returns {Promise}
- */
- create: (access, data) => {
- const create_certificate = data.certificate_id === 'new';
-
- if (create_certificate) {
- delete data.certificate_id;
- }
-
- return access
- .can('redirection_hosts:create', data)
- .then((/* access_data */) => {
- // Get a list of the domain names and check each of them against existing records
- const domain_name_check_promises = [];
-
- data.domain_names.map(function (domain_name) {
- domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name));
- });
-
- return Promise.all(domain_name_check_promises).then((check_results) => {
- check_results.map(function (result) {
- if (result.is_taken) {
- throw new error.ValidationError(result.hostname + ' is already in use');
- }
- });
- });
- })
- .then(() => {
- // At this point the domains should have been checked
- data.owner_user_id = access.token.getUserId(1);
- data = internalHost.cleanSslHstsData(data);
-
- return redirectionHostModel.query().insertAndFetch(data).then(utils.omitRow(omissions()));
- })
- .then((row) => {
- if (create_certificate) {
- return internalCertificate
- .createQuickCertificate(access, data)
- .then((cert) => {
- // update host with cert id
- return internalRedirectionHost.update(access, {
- id: row.id,
- certificate_id: cert.id,
- });
- })
- .then(() => {
- return row;
- });
- }
- return row;
- })
- .then((row) => {
- // re-fetch with cert
- return internalRedirectionHost.get(access, {
- id: row.id,
- expand: ['certificate', 'owner'],
- });
- })
- .then((row) => {
- // Configure nginx
- return internalNginx.configure(redirectionHostModel, 'redirection_host', row).then(() => {
- return row;
- });
- })
- .then((row) => {
- data.meta = _.assign({}, data.meta || {}, row.meta);
-
- // Add to audit log
- return internalAuditLog
- .add(access, {
- action: 'created',
- object_type: 'redirection-host',
- object_id: row.id,
- meta: data,
- })
- .then(() => {
- return row;
- });
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @return {Promise}
- */
- update: (access, data) => {
- const create_certificate = data.certificate_id === 'new';
-
- if (create_certificate) {
- delete data.certificate_id;
- }
-
- return access
- .can('redirection_hosts:update', data.id)
- .then((/* access_data */) => {
- // Get a list of the domain names and check each of them against existing records
- const domain_name_check_promises = [];
-
- if (typeof data.domain_names !== 'undefined') {
- data.domain_names.map(function (domain_name) {
- domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'redirection', data.id));
- });
-
- return Promise.all(domain_name_check_promises).then((check_results) => {
- check_results.map(function (result) {
- if (result.is_taken) {
- throw new error.ValidationError(result.hostname + ' is already in use');
- }
- });
- });
- }
- })
- .then(() => {
- return internalRedirectionHost.get(access, { id: data.id });
- })
- .then((row) => {
- if (row.id !== data.id) {
- // Sanity check that something crazy hasn't happened
- throw new error.InternalValidationError('Redirection Host could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
- }
-
- if (create_certificate) {
- return internalCertificate
- .createQuickCertificate(access, {
- domain_names: data.domain_names || row.domain_names,
- meta: _.assign({}, row.meta, data.meta),
- })
- .then((cert) => {
- // update host with cert id
- data.certificate_id = cert.id;
- })
- .then(() => {
- return row;
- });
- } else {
- return row;
- }
- })
- .then((row) => {
- // Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here.
- data = _.assign(
- {},
- {
- domain_names: row.domain_names,
- },
- data,
- );
-
- data = internalHost.cleanSslHstsData(data, row);
-
- return redirectionHostModel
- .query()
- .where({ id: data.id })
- .patch(data)
- .then((saved_row) => {
- // Add to audit log
- return internalAuditLog
- .add(access, {
- action: 'updated',
- object_type: 'redirection-host',
- object_id: row.id,
- meta: data,
- })
- .then(() => {
- return _.omit(saved_row, omissions());
- });
- });
- })
- .then(() => {
- return internalRedirectionHost
- .get(access, {
- id: data.id,
- expand: ['owner', 'certificate'],
- })
- .then((row) => {
- // Configure nginx
- return internalNginx.configure(redirectionHostModel, 'redirection_host', row).then((new_meta) => {
- row.meta = new_meta;
- row = internalHost.cleanRowCertificateMeta(row);
- return _.omit(row, omissions());
- });
- });
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @param {Array} [data.expand]
- * @param {Array} [data.omit]
- * @return {Promise}
- */
- get: (access, data) => {
- if (typeof data === 'undefined') {
- data = {};
- }
-
- return access
- .can('redirection_hosts:get', data.id)
- .then((access_data) => {
- const query = redirectionHostModel.query().where('is_deleted', 0).andWhere('id', data.id).allowGraph('[owner,certificate]').first();
-
- if (access_data.permission_visibility !== 'all') {
- query.andWhere('owner_user_id', access.token.getUserId(1));
- }
-
- if (typeof data.expand !== 'undefined' && data.expand !== null) {
- query.withGraphFetched('[' + data.expand.join(', ') + ']');
- }
-
- return query.then(utils.omitRow(omissions()));
- })
- .then((row) => {
- if (!row) {
- throw new error.ItemNotFoundError(data.id);
- }
- row = internalHost.cleanRowCertificateMeta(row);
- // Custom omissions
- if (typeof data.omit !== 'undefined' && data.omit !== null) {
- row = _.omit(row, data.omit);
- }
- return row;
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @param {String} [data.reason]
- * @returns {Promise}
- */
- delete: (access, data) => {
- return access
- .can('redirection_hosts:delete', data.id)
- .then(() => {
- return internalRedirectionHost.get(access, { id: data.id });
- })
- .then((row) => {
- if (!row) {
- throw new error.ItemNotFoundError(data.id);
- }
-
- return redirectionHostModel
- .query()
- .where('id', row.id)
- .patch({
- is_deleted: 1,
- })
- .then(() => {
- // Delete Nginx Config
- return internalNginx.deleteConfig('redirection_host', row).then(() => {
- return internalNginx.reload();
- });
- })
- .then(() => {
- // Add to audit log
- return internalAuditLog.add(access, {
- action: 'deleted',
- object_type: 'redirection-host',
- object_id: row.id,
- meta: _.omit(row, omissions()),
- });
- });
- })
- .then(() => {
- return true;
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @param {String} [data.reason]
- * @returns {Promise}
- */
- enable: (access, data) => {
- return access
- .can('redirection_hosts:update', data.id)
- .then(() => {
- return internalRedirectionHost.get(access, {
- id: data.id,
- expand: ['certificate', 'owner'],
- });
- })
- .then((row) => {
- if (!row) {
- throw new error.ItemNotFoundError(data.id);
- } else if (row.enabled) {
- throw new error.ValidationError('Host is already enabled');
- }
-
- row.enabled = 1;
-
- return redirectionHostModel
- .query()
- .where('id', row.id)
- .patch({
- enabled: 1,
- })
- .then(() => {
- // Configure nginx
- return internalNginx.configure(redirectionHostModel, 'redirection_host', row);
- })
- .then(() => {
- // Add to audit log
- return internalAuditLog.add(access, {
- action: 'enabled',
- object_type: 'redirection-host',
- object_id: row.id,
- meta: _.omit(row, omissions()),
- });
- });
- })
- .then(() => {
- return true;
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @param {String} [data.reason]
- * @returns {Promise}
- */
- disable: (access, data) => {
- return access
- .can('redirection_hosts:update', data.id)
- .then(() => {
- return internalRedirectionHost.get(access, { id: data.id });
- })
- .then((row) => {
- if (!row) {
- throw new error.ItemNotFoundError(data.id);
- } else if (!row.enabled) {
- throw new error.ValidationError('Host is already disabled');
- }
-
- row.enabled = 0;
-
- return redirectionHostModel
- .query()
- .where('id', row.id)
- .patch({
- enabled: 0,
- })
- .then(() => {
- // Delete Nginx Config
- return internalNginx.deleteConfig('redirection_host', row).then(() => {
- return internalNginx.reload();
- });
- })
- .then(() => {
- // Add to audit log
- return internalAuditLog.add(access, {
- action: 'disabled',
- object_type: 'redirection-host',
- object_id: row.id,
- meta: _.omit(row, omissions()),
- });
- });
- })
- .then(() => {
- return true;
- });
- },
-
- /**
- * All Hosts
- *
- * @param {Access} access
- * @param {Array} [expand]
- * @param {String} [search_query]
- * @returns {Promise}
- */
- getAll: (access, expand, search_query) => {
- return access
- .can('redirection_hosts:list')
- .then((access_data) => {
- const query = redirectionHostModel.query().where('is_deleted', 0).groupBy('id').allowGraph('[owner,certificate]').orderBy('domain_names', 'ASC');
-
- if (access_data.permission_visibility !== 'all') {
- query.andWhere('owner_user_id', access.token.getUserId(1));
- }
-
- // Query is used for searching
- if (typeof search_query === 'string') {
- query.where(function () {
- this.where('domain_names', 'like', '%' + search_query + '%');
- });
- }
-
- if (typeof expand !== 'undefined' && expand !== null) {
- query.withGraphFetched('[' + expand.join(', ') + ']');
- }
-
- return query.then(utils.omitRows(omissions()));
- })
- .then((rows) => {
- if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
- return internalHost.cleanAllRowsCertificateMeta(rows);
- }
-
- return rows;
- });
- },
-
- /**
- * Report use
- *
- * @param {Number} user_id
- * @param {String} visibility
- * @returns {Promise}
- */
- getCount: (user_id, visibility) => {
- const query = redirectionHostModel.query().count('id as count').where('is_deleted', 0);
-
- if (visibility !== 'all') {
- query.andWhere('owner_user_id', user_id);
- }
-
- return query.first().then((row) => {
- return parseInt(row.count, 10);
- });
- },
-};
-
-module.exports = internalRedirectionHost;
diff --git a/backend/internal/report.js b/backend/internal/report.js
deleted file mode 100644
index 9eda4cf50..000000000
--- a/backend/internal/report.js
+++ /dev/null
@@ -1,32 +0,0 @@
-const internalProxyHost = require('./proxy-host');
-const internalRedirectionHost = require('./redirection-host');
-const internalDeadHost = require('./dead-host');
-const internalStream = require('./stream');
-
-const internalReport = {
- /**
- * @param {Access} access
- * @return {Promise}
- */
- getHostsReport: (access) => {
- return access
- .can('reports:hosts', 1)
- .then((access_data) => {
- const user_id = access.token.getUserId(1);
-
- const promises = [internalProxyHost.getCount(user_id, access_data.visibility), internalRedirectionHost.getCount(user_id, access_data.visibility), internalStream.getCount(user_id, access_data.visibility), internalDeadHost.getCount(user_id, access_data.visibility)];
-
- return Promise.all(promises);
- })
- .then((counts) => {
- return {
- proxy: counts.shift(),
- redirection: counts.shift(),
- stream: counts.shift(),
- dead: counts.shift(),
- };
- });
- },
-};
-
-module.exports = internalReport;
diff --git a/backend/internal/setting.js b/backend/internal/setting.js
deleted file mode 100644
index e0c379374..000000000
--- a/backend/internal/setting.js
+++ /dev/null
@@ -1,125 +0,0 @@
-const fs = require('fs');
-const error = require('../lib/error');
-const settingModel = require('../models/setting');
-const internalNginx = require('./nginx');
-
-const internalSetting = {
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {String} data.id
- * @return {Promise}
- */
- update: (access, data) => {
- return access
- .can('settings:update', data.id)
- .then((/* access_data */) => {
- return internalSetting.get(access, { id: data.id });
- })
- .then((row) => {
- if (row.id !== data.id) {
- // Sanity check that something crazy hasn't happened
- throw new error.InternalValidationError('Setting could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
- }
-
- return settingModel.query().where({ id: data.id }).patch(data);
- })
- .then(() => {
- return internalSetting.get(access, {
- id: data.id,
- });
- })
- .then((row) => {
- if (row.id === 'default-site') {
- // write the html if we need to
- if (row.value === 'html') {
- fs.writeFileSync('/data/etc/html/index.html', row.meta.html, { encoding: 'utf8' });
- }
-
- // Configure nginx
- return internalNginx
- .deleteConfig('default')
- .then(() => {
- return internalNginx.generateConfig('default', row);
- })
- .then(() => {
- return internalNginx.test();
- })
- .then(() => {
- return internalNginx.reload();
- })
- .then(() => {
- return row;
- })
- .catch((/* err */) => {
- internalNginx
- .deleteConfig('default')
- .then(() => {
- return internalNginx.test();
- })
- .then(() => {
- return internalNginx.reload();
- })
- .then(() => {
- // I'm being slack here I know..
- throw new error.ValidationError('Could not reconfigure Nginx. Please check logs.');
- });
- });
- } else {
- return row;
- }
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {String} data.id
- * @return {Promise}
- */
- get: (access, data) => {
- return access
- .can('settings:get', data.id)
- .then(() => {
- return settingModel.query().where('id', data.id).first();
- })
- .then((row) => {
- if (row) {
- return row;
- } else {
- throw new error.ItemNotFoundError(data.id);
- }
- });
- },
-
- /**
- * This will only count the settings
- *
- * @param {Access} access
- * @returns {*}
- */
- getCount: (access) => {
- return access
- .can('settings:list')
- .then(() => {
- return settingModel.query().count('id as count').first();
- })
- .then((row) => {
- return parseInt(row.count, 10);
- });
- },
-
- /**
- * All settings
- *
- * @param {Access} access
- * @returns {Promise}
- */
- getAll: (access) => {
- return access.can('settings:list').then(() => {
- return settingModel.query().orderBy('description', 'ASC');
- });
- },
-};
-
-module.exports = internalSetting;
diff --git a/backend/internal/stream.js b/backend/internal/stream.js
deleted file mode 100644
index 5b0413713..000000000
--- a/backend/internal/stream.js
+++ /dev/null
@@ -1,331 +0,0 @@
-const _ = require('lodash');
-const error = require('../lib/error');
-const utils = require('../lib/utils');
-const streamModel = require('../models/stream');
-const internalNginx = require('./nginx');
-const internalAuditLog = require('./audit-log');
-
-function omissions() {
- return ['is_deleted'];
-}
-
-const internalStream = {
- /**
- * @param {Access} access
- * @param {Object} data
- * @returns {Promise}
- */
- create: (access, data) => {
- return access
- .can('streams:create', data)
- .then((/* access_data */) => {
- // TODO: At this point the existing ports should have been checked
- data.owner_user_id = access.token.getUserId(1);
-
- if (typeof data.meta === 'undefined') {
- data.meta = {};
- }
-
- return streamModel.query().insertAndFetch(data).then(utils.omitRow(omissions()));
- })
- .then((row) => {
- // Configure nginx
- return internalNginx.configure(streamModel, 'stream', row).then(() => {
- return internalStream.get(access, { id: row.id, expand: ['owner'] });
- });
- })
- .then((row) => {
- // Add to audit log
- return internalAuditLog
- .add(access, {
- action: 'created',
- object_type: 'stream',
- object_id: row.id,
- meta: data,
- })
- .then(() => {
- return row;
- });
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @return {Promise}
- */
- update: (access, data) => {
- return access
- .can('streams:update', data.id)
- .then((/* access_data */) => {
- // TODO: at this point the existing streams should have been checked
- return internalStream.get(access, { id: data.id });
- })
- .then((row) => {
- if (row.id !== data.id) {
- // Sanity check that something crazy hasn't happened
- throw new error.InternalValidationError('Stream could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
- }
-
- return streamModel
- .query()
- .patchAndFetchById(row.id, data)
- .then(utils.omitRow(omissions()))
- .then((saved_row) => {
- return internalNginx.configure(streamModel, 'stream', saved_row).then(() => {
- return internalStream.get(access, { id: row.id, expand: ['owner'] });
- });
- })
- .then((saved_row) => {
- // Add to audit log
- return internalAuditLog
- .add(access, {
- action: 'updated',
- object_type: 'stream',
- object_id: row.id,
- meta: data,
- })
- .then(() => {
- return saved_row;
- });
- });
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @param {Array} [data.expand]
- * @param {Array} [data.omit]
- * @return {Promise}
- */
- get: (access, data) => {
- if (typeof data === 'undefined') {
- data = {};
- }
-
- return access
- .can('streams:get', data.id)
- .then((access_data) => {
- const query = streamModel.query().where('is_deleted', 0).andWhere('id', data.id).allowGraph('[owner]').first();
-
- if (access_data.permission_visibility !== 'all') {
- query.andWhere('owner_user_id', access.token.getUserId(1));
- }
-
- if (typeof data.expand !== 'undefined' && data.expand !== null) {
- query.withGraphFetched('[' + data.expand.join(', ') + ']');
- }
-
- return query.then(utils.omitRow(omissions()));
- })
- .then((row) => {
- if (!row) {
- throw new error.ItemNotFoundError(data.id);
- }
- // Custom omissions
- if (typeof data.omit !== 'undefined' && data.omit !== null) {
- row = _.omit(row, data.omit);
- }
- return row;
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @param {String} [data.reason]
- * @returns {Promise}
- */
- delete: (access, data) => {
- return access
- .can('streams:delete', data.id)
- .then(() => {
- return internalStream.get(access, { id: data.id });
- })
- .then((row) => {
- if (!row) {
- throw new error.ItemNotFoundError(data.id);
- }
-
- return streamModel
- .query()
- .where('id', row.id)
- .patch({
- is_deleted: 1,
- })
- .then(() => {
- // Delete Nginx Config
- return internalNginx.deleteConfig('stream', row).then(() => {
- return internalNginx.reload();
- });
- })
- .then(() => {
- // Add to audit log
- return internalAuditLog.add(access, {
- action: 'deleted',
- object_type: 'stream',
- object_id: row.id,
- meta: _.omit(row, omissions()),
- });
- });
- })
- .then(() => {
- return true;
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @param {String} [data.reason]
- * @returns {Promise}
- */
- enable: (access, data) => {
- return access
- .can('streams:update', data.id)
- .then(() => {
- return internalStream.get(access, {
- id: data.id,
- expand: ['owner'],
- });
- })
- .then((row) => {
- if (!row) {
- throw new error.ItemNotFoundError(data.id);
- } else if (row.enabled) {
- throw new error.ValidationError('Host is already enabled');
- }
-
- row.enabled = 1;
-
- return streamModel
- .query()
- .where('id', row.id)
- .patch({
- enabled: 1,
- })
- .then(() => {
- // Configure nginx
- return internalNginx.configure(streamModel, 'stream', row);
- })
- .then(() => {
- // Add to audit log
- return internalAuditLog.add(access, {
- action: 'enabled',
- object_type: 'stream',
- object_id: row.id,
- meta: _.omit(row, omissions()),
- });
- });
- })
- .then(() => {
- return true;
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Number} data.id
- * @param {String} [data.reason]
- * @returns {Promise}
- */
- disable: (access, data) => {
- return access
- .can('streams:update', data.id)
- .then(() => {
- return internalStream.get(access, { id: data.id });
- })
- .then((row) => {
- if (!row) {
- throw new error.ItemNotFoundError(data.id);
- } else if (!row.enabled) {
- throw new error.ValidationError('Host is already disabled');
- }
-
- row.enabled = 0;
-
- return streamModel
- .query()
- .where('id', row.id)
- .patch({
- enabled: 0,
- })
- .then(() => {
- // Delete Nginx Config
- return internalNginx.deleteConfig('stream', row).then(() => {
- return internalNginx.reload();
- });
- })
- .then(() => {
- // Add to audit log
- return internalAuditLog.add(access, {
- action: 'disabled',
- object_type: 'stream-host',
- object_id: row.id,
- meta: _.omit(row, omissions()),
- });
- });
- })
- .then(() => {
- return true;
- });
- },
-
- /**
- * All Streams
- *
- * @param {Access} access
- * @param {Array} [expand]
- * @param {String} [search_query]
- * @returns {Promise}
- */
- getAll: (access, expand, search_query) => {
- return access.can('streams:list').then((access_data) => {
- const query = streamModel.query().where('is_deleted', 0).groupBy('id').allowGraph('[owner]').orderBy('incoming_port', 'ASC');
-
- if (access_data.permission_visibility !== 'all') {
- query.andWhere('owner_user_id', access.token.getUserId(1));
- }
-
- // Query is used for searching
- if (typeof search_query === 'string') {
- query.where(function () {
- this.where('incoming_port', 'like', '%' + search_query + '%');
- });
- }
-
- if (typeof expand !== 'undefined' && expand !== null) {
- query.withGraphFetched('[' + expand.join(', ') + ']');
- }
-
- return query.then(utils.omitRows(omissions()));
- });
- },
-
- /**
- * Report use
- *
- * @param {Number} user_id
- * @param {String} visibility
- * @returns {Promise}
- */
- getCount: (user_id, visibility) => {
- const query = streamModel.query().count('id as count').where('is_deleted', 0);
-
- if (visibility !== 'all') {
- query.andWhere('owner_user_id', user_id);
- }
-
- return query.first().then((row) => {
- return parseInt(row.count, 10);
- });
- },
-};
-
-module.exports = internalStream;
diff --git a/backend/internal/token.js b/backend/internal/token.js
deleted file mode 100644
index 003943700..000000000
--- a/backend/internal/token.js
+++ /dev/null
@@ -1,155 +0,0 @@
-const _ = require('lodash');
-const error = require('../lib/error');
-const userModel = require('../models/user');
-const authModel = require('../models/auth');
-const helpers = require('../lib/helpers');
-const TokenModel = require('../models/token');
-
-module.exports = {
- /**
- * @param {Object} data
- * @param {String} data.identity
- * @param {String} data.secret
- * @param {String} [data.scope]
- * @param {String} [data.expiry]
- * @param {String} [issuer]
- * @returns {Promise}
- */
- getTokenFromEmail: (data, issuer) => {
- const Token = new TokenModel();
-
- data.scope = data.scope || 'user';
- data.expiry = data.expiry || '1d';
-
- return userModel
- .query()
- .where('email', data.identity.toLowerCase().trim())
- .andWhere('is_deleted', 0)
- .andWhere('is_disabled', 0)
- .first()
- .then((user) => {
- if (user) {
- // Get auth
- return authModel
- .query()
- .where('user_id', '=', user.id)
- .where('type', '=', 'password')
- .first()
- .then((auth) => {
- if (auth) {
- return auth.verifyPassword(data.secret).then((valid) => {
- if (valid) {
- if (data.scope !== 'user' && _.indexOf(user.roles, data.scope) === -1) {
- // The scope requested doesn't exist as a role against the user,
- // you shall not pass.
- throw new error.AuthError('Invalid scope: ' + data.scope);
- }
-
- // Create a moment of the expiry expression
- const expiry = helpers.parseDatePeriod(data.expiry);
- if (expiry === null) {
- throw new error.AuthError('Invalid expiry time: ' + data.expiry);
- }
-
- return Token.create({
- iss: issuer || 'api',
- attrs: {
- id: user.id,
- },
- scope: [data.scope],
- expiresIn: data.expiry,
- }).then((signed) => {
- return {
- token: signed.token,
- expires: expiry.toISOString(),
- };
- });
- } else {
- throw new error.AuthError('Invalid password');
- }
- });
- } else {
- throw new error.AuthError('No password auth for user');
- }
- });
- } else {
- throw new error.AuthError('No relevant user found');
- }
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} [data]
- * @param {String} [data.expiry]
- * @param {String} [data.scope] Only considered if existing token scope is admin
- * @returns {Promise}
- */
- getFreshToken: (access, data) => {
- const Token = new TokenModel();
-
- data = data || {};
- data.expiry = data.expiry || '1d';
-
- if (access && access.token.getUserId(0)) {
- // Create a moment of the expiry expression
- const expiry = helpers.parseDatePeriod(data.expiry);
- if (expiry === null) {
- throw new error.AuthError('Invalid expiry time: ' + data.expiry);
- }
-
- const token_attrs = {
- id: access.token.getUserId(0),
- };
-
- // Only admins can request otherwise scoped tokens
- let scope = access.token.get('scope');
- if (data.scope && access.token.hasScope('admin')) {
- scope = [data.scope];
-
- if (data.scope === 'job-board' || data.scope === 'worker') {
- token_attrs.id = 0;
- }
- }
-
- return Token.create({
- iss: 'api',
- scope,
- attrs: token_attrs,
- expiresIn: data.expiry,
- }).then((signed) => {
- return {
- token: signed.token,
- expires: expiry.toISOString(),
- };
- });
- } else {
- throw new error.AssertionFailedError('Existing token contained invalid user data');
- }
- },
-
- /**
- * @param {Object} user
- * @returns {Promise}
- */
- getTokenFromUser: (user) => {
- const expire = '1d';
- const Token = new TokenModel();
- const expiry = helpers.parseDatePeriod(expire);
-
- return Token.create({
- iss: 'api',
- attrs: {
- id: user.id,
- },
- scope: ['user'],
- expiresIn: expire,
- }).then((signed) => {
- return {
- token: signed.token,
- expires: expiry.toISOString(),
- user,
- };
- });
- },
-};
diff --git a/backend/internal/user.js b/backend/internal/user.js
deleted file mode 100644
index 0992b22db..000000000
--- a/backend/internal/user.js
+++ /dev/null
@@ -1,482 +0,0 @@
-const _ = require('lodash');
-const error = require('../lib/error');
-const utils = require('../lib/utils');
-const userModel = require('../models/user');
-const userPermissionModel = require('../models/user_permission');
-const authModel = require('../models/auth');
-const gravatar = require('gravatar');
-const internalToken = require('./token');
-const internalAuditLog = require('./audit-log');
-
-function omissions() {
- return ['is_deleted'];
-}
-
-const internalUser = {
- /**
- * @param {Access} access
- * @param {Object} data
- * @returns {Promise}
- */
- create: (access, data) => {
- const auth = data.auth || null;
- delete data.auth;
-
- data.avatar = data.avatar || '';
- data.roles = data.roles || [];
-
- if (typeof data.is_disabled !== 'undefined') {
- data.is_disabled = data.is_disabled ? 1 : 0;
- }
-
- return access
- .can('users:create', data)
- .then(() => {
- data.avatar = gravatar.url(data.email, { default: 'mm' });
-
- return userModel.query().insertAndFetch(data).then(utils.omitRow(omissions()));
- })
- .then((user) => {
- if (auth) {
- return authModel
- .query()
- .insert({
- user_id: user.id,
- type: auth.type,
- secret: auth.secret,
- meta: {},
- })
- .then(() => {
- return user;
- });
- } else {
- return user;
- }
- })
- .then((user) => {
- // Create permissions row as well
- const is_admin = data.roles.indexOf('admin') !== -1;
-
- return userPermissionModel
- .query()
- .insert({
- user_id: user.id,
- visibility: is_admin ? 'all' : 'user',
- proxy_hosts: 'manage',
- redirection_hosts: 'manage',
- dead_hosts: 'manage',
- streams: 'manage',
- access_lists: 'manage',
- certificates: 'manage',
- })
- .then(() => {
- return internalUser.get(access, { id: user.id, expand: ['permissions'] });
- });
- })
- .then((user) => {
- // Add to audit log
- return internalAuditLog
- .add(access, {
- action: 'created',
- object_type: 'user',
- object_id: user.id,
- meta: user,
- })
- .then(() => {
- return user;
- });
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Integer} data.id
- * @param {String} [data.email]
- * @param {String} [data.name]
- * @return {Promise}
- */
- update: (access, data) => {
- if (typeof data.is_disabled !== 'undefined') {
- data.is_disabled = data.is_disabled ? 1 : 0;
- }
-
- return access
- .can('users:update', data.id)
- .then(() => {
- // Make sure that the user being updated doesn't change their email to another user that is already using it
- // 1. get user we want to update
- return internalUser.get(access, { id: data.id }).then((user) => {
- // 2. if email is to be changed, find other users with that email
- if (typeof data.email !== 'undefined') {
- data.email = data.email.toLowerCase().trim();
-
- if (user.email !== data.email) {
- return internalUser.isEmailAvailable(data.email, data.id).then((available) => {
- if (!available) {
- throw new error.ValidationError('Email address already in use - ' + data.email);
- }
-
- return user;
- });
- }
- }
-
- // No change to email:
- return user;
- });
- })
- .then((user) => {
- if (user.id !== data.id) {
- // Sanity check that something crazy hasn't happened
- throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id);
- }
-
- data.avatar = gravatar.url(data.email || user.email, { default: 'mm' });
-
- return userModel.query().patchAndFetchById(user.id, data).then(utils.omitRow(omissions()));
- })
- .then(() => {
- return internalUser.get(access, { id: data.id });
- })
- .then((user) => {
- // Add to audit log
- return internalAuditLog
- .add(access, {
- action: 'updated',
- object_type: 'user',
- object_id: user.id,
- meta: data,
- })
- .then(() => {
- return user;
- });
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} [data]
- * @param {Integer} [data.id] Defaults to the token user
- * @param {Array} [data.expand]
- * @param {Array} [data.omit]
- * @return {Promise}
- */
- get: (access, data) => {
- if (typeof data === 'undefined') {
- data = {};
- }
-
- if (typeof data.id === 'undefined' || !data.id) {
- data.id = access.token.getUserId(0);
- }
-
- return access
- .can('users:get', data.id)
- .then(() => {
- const query = userModel.query().where('is_deleted', 0).andWhere('id', data.id).allowGraph('[permissions]').first();
-
- if (typeof data.expand !== 'undefined' && data.expand !== null) {
- query.withGraphFetched('[' + data.expand.join(', ') + ']');
- }
-
- return query.then(utils.omitRow(omissions()));
- })
- .then((row) => {
- if (!row) {
- throw new error.ItemNotFoundError(data.id);
- }
- // Custom omissions
- if (typeof data.omit !== 'undefined' && data.omit !== null) {
- row = _.omit(row, data.omit);
- }
- return row;
- });
- },
-
- /**
- * Checks if an email address is available, but if a user_id is supplied, it will ignore checking
- * against that user.
- *
- * @param email
- * @param user_id
- */
- isEmailAvailable: (email, user_id) => {
- const query = userModel.query().where('email', '=', email.toLowerCase().trim()).where('is_deleted', 0).first();
-
- if (typeof user_id !== 'undefined') {
- query.where('id', '!=', user_id);
- }
-
- return query.then((user) => {
- return !user;
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Integer} data.id
- * @param {String} [data.reason]
- * @returns {Promise}
- */
- delete: (access, data) => {
- return access
- .can('users:delete', data.id)
- .then(() => {
- return internalUser.get(access, { id: data.id });
- })
- .then((user) => {
- if (!user) {
- throw new error.ItemNotFoundError(data.id);
- }
-
- // Make sure user can't delete themselves
- if (user.id === access.token.getUserId(0)) {
- throw new error.PermissionError('You cannot delete yourself.');
- }
-
- return userModel
- .query()
- .where('id', user.id)
- .patch({
- is_deleted: 1,
- })
- .then(() => {
- // Add to audit log
- return internalAuditLog.add(access, {
- action: 'deleted',
- object_type: 'user',
- object_id: user.id,
- meta: _.omit(user, omissions()),
- });
- });
- })
- .then(() => {
- return true;
- });
- },
-
- /**
- * This will only count the users
- *
- * @param {Access} access
- * @param {String} [search_query]
- * @returns {*}
- */
- getCount: (access, search_query) => {
- return access
- .can('users:list')
- .then(() => {
- const query = userModel.query().count('id as count').where('is_deleted', 0).first();
-
- // Query is used for searching
- if (typeof search_query === 'string') {
- query.where(function () {
- this.where('user.name', 'like', '%' + search_query + '%').orWhere('user.email', 'like', '%' + search_query + '%');
- });
- }
-
- return query;
- })
- .then((row) => {
- return parseInt(row.count, 10);
- });
- },
-
- /**
- * All users
- *
- * @param {Access} access
- * @param {Array} [expand]
- * @param {String} [search_query]
- * @returns {Promise}
- */
- getAll: (access, expand, search_query) => {
- return access.can('users:list').then(() => {
- const query = userModel.query().where('is_deleted', 0).groupBy('id').allowGraph('[permissions]').orderBy('name', 'ASC');
-
- // Query is used for searching
- if (typeof search_query === 'string') {
- query.where(function () {
- this.where('name', 'like', '%' + search_query + '%').orWhere('email', 'like', '%' + search_query + '%');
- });
- }
-
- if (typeof expand !== 'undefined' && expand !== null) {
- query.withGraphFetched('[' + expand.join(', ') + ']');
- }
-
- return query.then(utils.omitRows(omissions()));
- });
- },
-
- /**
- * @param {Access} access
- * @param {Integer} [id_requested]
- * @returns {[String]}
- */
- getUserOmisionsByAccess: (access, id_requested) => {
- let response = []; // Admin response
-
- if (!access.token.hasScope('admin') && access.token.getUserId(0) !== id_requested) {
- response = ['roles', 'is_deleted']; // Restricted response
- }
-
- return response;
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Integer} data.id
- * @param {String} data.type
- * @param {String} data.secret
- * @return {Promise}
- */
- setPassword: (access, data) => {
- return access
- .can('users:password', data.id)
- .then(() => {
- return internalUser.get(access, { id: data.id });
- })
- .then((user) => {
- if (user.id !== data.id) {
- // Sanity check that something crazy hasn't happened
- throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id);
- }
-
- if (user.id === access.token.getUserId(0)) {
- // they're setting their own password. Make sure their current password is correct
- if (typeof data.current === 'undefined' || !data.current) {
- throw new error.ValidationError('Current password was not supplied');
- }
-
- return internalToken
- .getTokenFromEmail({
- identity: user.email,
- secret: data.current,
- })
- .then(() => {
- return user;
- });
- }
-
- return user;
- })
- .then((user) => {
- // Get auth, patch if it exists
- return authModel
- .query()
- .where('user_id', user.id)
- .andWhere('type', data.type)
- .first()
- .then((existing_auth) => {
- if (existing_auth) {
- // patch
- return authModel.query().where('user_id', user.id).andWhere('type', data.type).patch({
- type: data.type, // This is required for the model to encrypt on save
- secret: data.secret,
- });
- } else {
- // insert
- return authModel.query().insert({
- user_id: user.id,
- type: data.type,
- secret: data.secret,
- meta: {},
- });
- }
- })
- .then(() => {
- // Add to Audit Log
- return internalAuditLog.add(access, {
- action: 'updated',
- object_type: 'user',
- object_id: user.id,
- meta: {
- name: user.name,
- password_changed: true,
- auth_type: data.type,
- },
- });
- });
- })
- .then(() => {
- return true;
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @return {Promise}
- */
- setPermissions: (access, data) => {
- return access
- .can('users:permissions', data.id)
- .then(() => {
- return internalUser.get(access, { id: data.id });
- })
- .then((user) => {
- if (user.id !== data.id) {
- // Sanity check that something crazy hasn't happened
- throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id);
- }
-
- return user;
- })
- .then((user) => {
- // Get perms row, patch if it exists
- return userPermissionModel
- .query()
- .where('user_id', user.id)
- .first()
- .then((existing_auth) => {
- if (existing_auth) {
- // patch
- return userPermissionModel
- .query()
- .where('user_id', user.id)
- .patchAndFetchById(existing_auth.id, _.assign({ user_id: user.id }, data));
- } else {
- // insert
- return userPermissionModel.query().insertAndFetch(_.assign({ user_id: user.id }, data));
- }
- })
- .then((permissions) => {
- // Add to Audit Log
- return internalAuditLog.add(access, {
- action: 'updated',
- object_type: 'user',
- object_id: user.id,
- meta: {
- name: user.name,
- permissions,
- },
- });
- });
- })
- .then(() => {
- return true;
- });
- },
-
- /**
- * @param {Access} access
- * @param {Object} data
- * @param {Integer} data.id
- */
- loginAs: (access, data) => {
- return access
- .can('users:loginas', data.id)
- .then(() => {
- return internalUser.get(access, data);
- })
- .then((user) => {
- return internalToken.getTokenFromUser(user);
- });
- },
-};
-
-module.exports = internalUser;
diff --git a/backend/knexfile.js b/backend/knexfile.js
deleted file mode 100644
index 3c33162e9..000000000
--- a/backend/knexfile.js
+++ /dev/null
@@ -1,19 +0,0 @@
-module.exports = {
- development: {
- client: 'mysql2',
- migrations: {
- tableName: 'migrations',
- stub: 'lib/migrate_template.js',
- directory: 'migrations',
- },
- },
-
- production: {
- client: 'mysql2',
- migrations: {
- tableName: 'migrations',
- stub: 'lib/migrate_template.js',
- directory: 'migrations',
- },
- },
-};
diff --git a/backend/lib/access.js b/backend/lib/access.js
deleted file mode 100644
index 5cd8d426c..000000000
--- a/backend/lib/access.js
+++ /dev/null
@@ -1,302 +0,0 @@
-/**
- * Some Notes: This is a friggin complicated piece of code.
- *
- * "scope" in this file means "where did this token come from and what is using it", so 99% of the time
- * the "scope" is going to be "user" because it would be a user token. This is not to be confused with
- * the "role" which could be "user" or "admin". The scope in fact, could be "worker" or anything else.
- *
- *
- */
-
-const _ = require('lodash');
-const logger = require('../logger').access;
-const error = require('./error');
-const userModel = require('../models/user');
-const proxyHostModel = require('../models/proxy_host');
-const TokenModel = require('../models/token');
-const roleSchema = require('./access/roles.json');
-const permsSchema = require('./access/permissions.json');
-
-const Ajv = require('ajv');
-const addFormats = require('ajv-formats');
-
-module.exports = function (token_string) {
- const Token = new TokenModel();
- let token_data = null;
- let initialized = false;
- const object_cache = {};
- let allow_internal_access = false;
- let user_roles = [];
- let permissions = {};
-
- /**
- * Loads the Token object from the token string
- *
- * @returns {Promise}
- */
- this.init = () => {
- return new Promise((resolve, reject) => {
- if (initialized) {
- resolve();
- } else if (!token_string) {
- reject(new error.PermissionError('Permission Denied'));
- } else {
- resolve(
- Token.load(token_string).then((data) => {
- token_data = data;
-
- // At this point we need to load the user from the DB and make sure they:
- // - exist (and not soft deleted)
- // - still have the appropriate scopes for this token
- // This is only required when the User ID is supplied or if the token scope has `user`
-
- if (token_data.attrs.id || (typeof token_data.scope !== 'undefined' && _.indexOf(token_data.scope, 'user') !== -1)) {
- // Has token user id or token user scope
- return userModel
- .query()
- .where('id', token_data.attrs.id)
- .andWhere('is_deleted', 0)
- .andWhere('is_disabled', 0)
- .allowGraph('[permissions]')
- .withGraphFetched('[permissions]')
- .first()
- .then((user) => {
- if (user) {
- // make sure user has all scopes of the token
- // The `user` role is not added against the user row, so we have to just add it here to get past this check.
- user.roles.push('user');
-
- let is_ok = true;
- _.forEach(token_data.scope, (scope_item) => {
- if (_.indexOf(user.roles, scope_item) === -1) {
- is_ok = false;
- }
- });
-
- if (!is_ok) {
- throw new error.AuthError('Invalid token scope for User');
- } else {
- initialized = true;
- user_roles = user.roles;
- permissions = user.permissions;
- }
- } else {
- throw new error.AuthError('User cannot be loaded for Token');
- }
- });
- } else {
- initialized = true;
- }
- }),
- );
- }
- });
- };
-
- /**
- * Fetches the object ids from the database, only once per object type, for this token.
- * This only applies to USER token scopes, as all other tokens are not really bound
- * by object scopes
- *
- * @param {String} object_type
- * @returns {Promise}
- */
- this.loadObjects = (object_type) => {
- return new Promise((resolve, reject) => {
- if (Token.hasScope('user')) {
- if (typeof token_data.attrs.id === 'undefined' || !token_data.attrs.id) {
- reject(new error.AuthError('User Token supplied without a User ID'));
- } else {
- const token_user_id = token_data.attrs.id ? token_data.attrs.id : 0;
- let query;
-
- if (typeof object_cache[object_type] === 'undefined') {
- switch (object_type) {
- // USERS - should only return yourself
- case 'users':
- resolve(token_user_id ? [token_user_id] : []);
- break;
-
- // Proxy Hosts
- case 'proxy_hosts':
- query = proxyHostModel.query().select('id').andWhere('is_deleted', 0);
-
- if (permissions.visibility === 'user') {
- query.andWhere('owner_user_id', token_user_id);
- }
-
- resolve(
- query.then((rows) => {
- const result = [];
- _.forEach(rows, (rule_row) => {
- result.push(rule_row.id);
- });
-
- // enum should not have less than 1 item
- if (!result.length) {
- result.push(0);
- }
-
- return result;
- }),
- );
- break;
-
- // DEFAULT: null
- default:
- resolve(null);
- break;
- }
- } else {
- resolve(object_cache[object_type]);
- }
- }
- } else {
- resolve(null);
- }
- }).then((objects) => {
- object_cache[object_type] = objects;
- return objects;
- });
- };
-
- /**
- * Creates a schema object on the fly with the IDs and other values required to be checked against the permissionSchema
- *
- * @param {String} permission_label
- * @returns {Object}
- */
- this.getObjectSchema = (permission_label) => {
- const base_object_type = permission_label.split(':').shift();
-
- const schema = {
- $id: 'objects',
- $schema: 'http://json-schema.org/draft-07/schema#',
- description: 'Actor Properties',
- type: 'object',
- additionalProperties: false,
- properties: {
- user_id: {
- anyOf: [
- {
- type: 'number',
- enum: [Token.get('attrs').id],
- },
- ],
- },
- scope: {
- type: 'string',
- pattern: '^' + Token.get('scope') + '$',
- },
- },
- };
-
- return this.loadObjects(base_object_type).then((object_result) => {
- if (typeof object_result === 'object' && object_result !== null) {
- schema.properties[base_object_type] = {
- type: 'number',
- enum: object_result,
- minimum: 1,
- };
- } else {
- schema.properties[base_object_type] = {
- type: 'number',
- minimum: 1,
- };
- }
-
- return schema;
- });
- };
-
- return {
- token: Token,
-
- /**
- *
- * @param {Boolean} [allow_internal]
- * @returns {Promise}
- */
- load: (allow_internal) => {
- return new Promise(function (resolve /*, reject */) {
- if (token_string) {
- resolve(Token.load(token_string));
- } else {
- allow_internal_access = allow_internal;
- resolve(allow_internal_access || null);
- }
- });
- },
-
- reloadObjects: this.loadObjects,
-
- /**
- *
- * @param {String} permission
- * @param {*} [data]
- * @returns {Promise}
- */
- can: (permission, data) => {
- if (allow_internal_access === true) {
- return Promise.resolve(true);
- // return true;
- } else {
- return this.init()
- .then(() => {
- // initialized, token decoded ok
- return this.getObjectSchema(permission).then((objectSchema) => {
- const data_schema = {
- [permission]: {
- data,
- scope: Token.get('scope'),
- roles: user_roles,
- permission_visibility: permissions.visibility,
- permission_proxy_hosts: permissions.proxy_hosts,
- permission_redirection_hosts: permissions.redirection_hosts,
- permission_dead_hosts: permissions.dead_hosts,
- permission_streams: permissions.streams,
- permission_access_lists: permissions.access_lists,
- permission_certificates: permissions.certificates,
- },
- };
-
- const permissionSchema = {
- $schema: 'http://json-schema.org/draft-07/schema#',
- $async: true,
- $id: 'permissions',
- additionalProperties: false,
- properties: {},
- };
-
- permissionSchema.properties[permission] = require('./access/' + permission.replace(/:/gim, '-') + '.json');
-
- // logger.info('objectSchema', JSON.stringify(objectSchema, null, 2));
- // logger.info('permissionSchema', JSON.stringify(permissionSchema, null, 2));
- // logger.info('data_schema', JSON.stringify(data_schema, null, 2));
-
- const ajv = new Ajv({
- verbose: true,
- allErrors: true,
- breakOnError: true,
- coerceTypes: true,
- schemas: [roleSchema, permsSchema, objectSchema, permissionSchema],
- strict: false,
- });
- addFormats(ajv);
-
- return ajv.validate('permissions', data_schema).then(() => {
- return data_schema[permission];
- });
- });
- })
- .catch((err) => {
- err.permission = permission;
- err.permission_data = data;
- logger.error(permission, data, err.message);
-
- throw new error.PermissionError('Permission Denied', err);
- });
- }
- },
- };
-};
diff --git a/backend/lib/access/access_lists-create.json b/backend/lib/access/access_lists-create.json
deleted file mode 100644
index 5a16a8642..000000000
--- a/backend/lib/access/access_lists-create.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_access_lists", "roles"],
- "properties": {
- "permission_access_lists": {
- "$ref": "perms#/definitions/manage"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/access_lists-delete.json b/backend/lib/access/access_lists-delete.json
deleted file mode 100644
index 5a16a8642..000000000
--- a/backend/lib/access/access_lists-delete.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_access_lists", "roles"],
- "properties": {
- "permission_access_lists": {
- "$ref": "perms#/definitions/manage"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/access_lists-get.json b/backend/lib/access/access_lists-get.json
deleted file mode 100644
index 8f6dd8cc6..000000000
--- a/backend/lib/access/access_lists-get.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_access_lists", "roles"],
- "properties": {
- "permission_access_lists": {
- "$ref": "perms#/definitions/view"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/access_lists-list.json b/backend/lib/access/access_lists-list.json
deleted file mode 100644
index 8f6dd8cc6..000000000
--- a/backend/lib/access/access_lists-list.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_access_lists", "roles"],
- "properties": {
- "permission_access_lists": {
- "$ref": "perms#/definitions/view"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/access_lists-update.json b/backend/lib/access/access_lists-update.json
deleted file mode 100644
index 5a16a8642..000000000
--- a/backend/lib/access/access_lists-update.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_access_lists", "roles"],
- "properties": {
- "permission_access_lists": {
- "$ref": "perms#/definitions/manage"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/auditlog-list.json b/backend/lib/access/auditlog-list.json
deleted file mode 100644
index aeadc94ba..000000000
--- a/backend/lib/access/auditlog-list.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- }
- ]
-}
diff --git a/backend/lib/access/certificates-create.json b/backend/lib/access/certificates-create.json
deleted file mode 100644
index bcdf66742..000000000
--- a/backend/lib/access/certificates-create.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_certificates", "roles"],
- "properties": {
- "permission_certificates": {
- "$ref": "perms#/definitions/manage"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/certificates-delete.json b/backend/lib/access/certificates-delete.json
deleted file mode 100644
index bcdf66742..000000000
--- a/backend/lib/access/certificates-delete.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_certificates", "roles"],
- "properties": {
- "permission_certificates": {
- "$ref": "perms#/definitions/manage"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/certificates-get.json b/backend/lib/access/certificates-get.json
deleted file mode 100644
index 9ccfa4f15..000000000
--- a/backend/lib/access/certificates-get.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_certificates", "roles"],
- "properties": {
- "permission_certificates": {
- "$ref": "perms#/definitions/view"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/certificates-list.json b/backend/lib/access/certificates-list.json
deleted file mode 100644
index 9ccfa4f15..000000000
--- a/backend/lib/access/certificates-list.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_certificates", "roles"],
- "properties": {
- "permission_certificates": {
- "$ref": "perms#/definitions/view"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/certificates-update.json b/backend/lib/access/certificates-update.json
deleted file mode 100644
index bcdf66742..000000000
--- a/backend/lib/access/certificates-update.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_certificates", "roles"],
- "properties": {
- "permission_certificates": {
- "$ref": "perms#/definitions/manage"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/dead_hosts-create.json b/backend/lib/access/dead_hosts-create.json
deleted file mode 100644
index a276c681d..000000000
--- a/backend/lib/access/dead_hosts-create.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_dead_hosts", "roles"],
- "properties": {
- "permission_dead_hosts": {
- "$ref": "perms#/definitions/manage"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/dead_hosts-delete.json b/backend/lib/access/dead_hosts-delete.json
deleted file mode 100644
index a276c681d..000000000
--- a/backend/lib/access/dead_hosts-delete.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_dead_hosts", "roles"],
- "properties": {
- "permission_dead_hosts": {
- "$ref": "perms#/definitions/manage"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/dead_hosts-get.json b/backend/lib/access/dead_hosts-get.json
deleted file mode 100644
index 87aa12e7d..000000000
--- a/backend/lib/access/dead_hosts-get.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_dead_hosts", "roles"],
- "properties": {
- "permission_dead_hosts": {
- "$ref": "perms#/definitions/view"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/dead_hosts-list.json b/backend/lib/access/dead_hosts-list.json
deleted file mode 100644
index 87aa12e7d..000000000
--- a/backend/lib/access/dead_hosts-list.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_dead_hosts", "roles"],
- "properties": {
- "permission_dead_hosts": {
- "$ref": "perms#/definitions/view"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/dead_hosts-update.json b/backend/lib/access/dead_hosts-update.json
deleted file mode 100644
index a276c681d..000000000
--- a/backend/lib/access/dead_hosts-update.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_dead_hosts", "roles"],
- "properties": {
- "permission_dead_hosts": {
- "$ref": "perms#/definitions/manage"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/permissions.json b/backend/lib/access/permissions.json
deleted file mode 100644
index 8480f9a1c..000000000
--- a/backend/lib/access/permissions.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "$schema": "http://json-schema.org/draft-07/schema#",
- "$id": "perms",
- "definitions": {
- "view": {
- "type": "string",
- "pattern": "^(view|manage)$"
- },
- "manage": {
- "type": "string",
- "pattern": "^(manage)$"
- }
- }
-}
diff --git a/backend/lib/access/proxy_hosts-create.json b/backend/lib/access/proxy_hosts-create.json
deleted file mode 100644
index 166527a39..000000000
--- a/backend/lib/access/proxy_hosts-create.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_proxy_hosts", "roles"],
- "properties": {
- "permission_proxy_hosts": {
- "$ref": "perms#/definitions/manage"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/proxy_hosts-delete.json b/backend/lib/access/proxy_hosts-delete.json
deleted file mode 100644
index 166527a39..000000000
--- a/backend/lib/access/proxy_hosts-delete.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_proxy_hosts", "roles"],
- "properties": {
- "permission_proxy_hosts": {
- "$ref": "perms#/definitions/manage"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/proxy_hosts-get.json b/backend/lib/access/proxy_hosts-get.json
deleted file mode 100644
index d88e4cfff..000000000
--- a/backend/lib/access/proxy_hosts-get.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_proxy_hosts", "roles"],
- "properties": {
- "permission_proxy_hosts": {
- "$ref": "perms#/definitions/view"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/proxy_hosts-list.json b/backend/lib/access/proxy_hosts-list.json
deleted file mode 100644
index d88e4cfff..000000000
--- a/backend/lib/access/proxy_hosts-list.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_proxy_hosts", "roles"],
- "properties": {
- "permission_proxy_hosts": {
- "$ref": "perms#/definitions/view"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/proxy_hosts-update.json b/backend/lib/access/proxy_hosts-update.json
deleted file mode 100644
index 166527a39..000000000
--- a/backend/lib/access/proxy_hosts-update.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_proxy_hosts", "roles"],
- "properties": {
- "permission_proxy_hosts": {
- "$ref": "perms#/definitions/manage"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/redirection_hosts-create.json b/backend/lib/access/redirection_hosts-create.json
deleted file mode 100644
index 342babc88..000000000
--- a/backend/lib/access/redirection_hosts-create.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_redirection_hosts", "roles"],
- "properties": {
- "permission_redirection_hosts": {
- "$ref": "perms#/definitions/manage"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/redirection_hosts-delete.json b/backend/lib/access/redirection_hosts-delete.json
deleted file mode 100644
index 342babc88..000000000
--- a/backend/lib/access/redirection_hosts-delete.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_redirection_hosts", "roles"],
- "properties": {
- "permission_redirection_hosts": {
- "$ref": "perms#/definitions/manage"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/redirection_hosts-get.json b/backend/lib/access/redirection_hosts-get.json
deleted file mode 100644
index ba2292064..000000000
--- a/backend/lib/access/redirection_hosts-get.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_redirection_hosts", "roles"],
- "properties": {
- "permission_redirection_hosts": {
- "$ref": "perms#/definitions/view"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/redirection_hosts-list.json b/backend/lib/access/redirection_hosts-list.json
deleted file mode 100644
index ba2292064..000000000
--- a/backend/lib/access/redirection_hosts-list.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_redirection_hosts", "roles"],
- "properties": {
- "permission_redirection_hosts": {
- "$ref": "perms#/definitions/view"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/redirection_hosts-update.json b/backend/lib/access/redirection_hosts-update.json
deleted file mode 100644
index 342babc88..000000000
--- a/backend/lib/access/redirection_hosts-update.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_redirection_hosts", "roles"],
- "properties": {
- "permission_redirection_hosts": {
- "$ref": "perms#/definitions/manage"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/reports-hosts.json b/backend/lib/access/reports-hosts.json
deleted file mode 100644
index dbc9e0c0f..000000000
--- a/backend/lib/access/reports-hosts.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/user"
- }
- ]
-}
diff --git a/backend/lib/access/roles.json b/backend/lib/access/roles.json
deleted file mode 100644
index 16b33b55b..000000000
--- a/backend/lib/access/roles.json
+++ /dev/null
@@ -1,39 +0,0 @@
-{
- "$schema": "http://json-schema.org/draft-07/schema#",
- "$id": "roles",
- "definitions": {
- "admin": {
- "type": "object",
- "required": ["scope", "roles"],
- "properties": {
- "scope": {
- "type": "array",
- "contains": {
- "type": "string",
- "pattern": "^user$"
- }
- },
- "roles": {
- "type": "array",
- "contains": {
- "type": "string",
- "pattern": "^admin$"
- }
- }
- }
- },
- "user": {
- "type": "object",
- "required": ["scope"],
- "properties": {
- "scope": {
- "type": "array",
- "contains": {
- "type": "string",
- "pattern": "^user$"
- }
- }
- }
- }
- }
-}
diff --git a/backend/lib/access/settings-get.json b/backend/lib/access/settings-get.json
deleted file mode 100644
index aeadc94ba..000000000
--- a/backend/lib/access/settings-get.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- }
- ]
-}
diff --git a/backend/lib/access/settings-list.json b/backend/lib/access/settings-list.json
deleted file mode 100644
index aeadc94ba..000000000
--- a/backend/lib/access/settings-list.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- }
- ]
-}
diff --git a/backend/lib/access/settings-update.json b/backend/lib/access/settings-update.json
deleted file mode 100644
index aeadc94ba..000000000
--- a/backend/lib/access/settings-update.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- }
- ]
-}
diff --git a/backend/lib/access/streams-create.json b/backend/lib/access/streams-create.json
deleted file mode 100644
index fbeb1cc91..000000000
--- a/backend/lib/access/streams-create.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_streams", "roles"],
- "properties": {
- "permission_streams": {
- "$ref": "perms#/definitions/manage"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/streams-delete.json b/backend/lib/access/streams-delete.json
deleted file mode 100644
index fbeb1cc91..000000000
--- a/backend/lib/access/streams-delete.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_streams", "roles"],
- "properties": {
- "permission_streams": {
- "$ref": "perms#/definitions/manage"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/streams-get.json b/backend/lib/access/streams-get.json
deleted file mode 100644
index 7e9962874..000000000
--- a/backend/lib/access/streams-get.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_streams", "roles"],
- "properties": {
- "permission_streams": {
- "$ref": "perms#/definitions/view"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/streams-list.json b/backend/lib/access/streams-list.json
deleted file mode 100644
index 7e9962874..000000000
--- a/backend/lib/access/streams-list.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_streams", "roles"],
- "properties": {
- "permission_streams": {
- "$ref": "perms#/definitions/view"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/streams-update.json b/backend/lib/access/streams-update.json
deleted file mode 100644
index fbeb1cc91..000000000
--- a/backend/lib/access/streams-update.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["permission_streams", "roles"],
- "properties": {
- "permission_streams": {
- "$ref": "perms#/definitions/manage"
- },
- "roles": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["user"]
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/users-create.json b/backend/lib/access/users-create.json
deleted file mode 100644
index aeadc94ba..000000000
--- a/backend/lib/access/users-create.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- }
- ]
-}
diff --git a/backend/lib/access/users-delete.json b/backend/lib/access/users-delete.json
deleted file mode 100644
index aeadc94ba..000000000
--- a/backend/lib/access/users-delete.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- }
- ]
-}
diff --git a/backend/lib/access/users-get.json b/backend/lib/access/users-get.json
deleted file mode 100644
index 2a2f0423a..000000000
--- a/backend/lib/access/users-get.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["data", "scope"],
- "properties": {
- "data": {
- "$ref": "objects#/properties/users"
- },
- "scope": {
- "type": "array",
- "contains": {
- "type": "string",
- "pattern": "^user$"
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/users-list.json b/backend/lib/access/users-list.json
deleted file mode 100644
index aeadc94ba..000000000
--- a/backend/lib/access/users-list.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- }
- ]
-}
diff --git a/backend/lib/access/users-loginas.json b/backend/lib/access/users-loginas.json
deleted file mode 100644
index aeadc94ba..000000000
--- a/backend/lib/access/users-loginas.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- }
- ]
-}
diff --git a/backend/lib/access/users-password.json b/backend/lib/access/users-password.json
deleted file mode 100644
index 2a2f0423a..000000000
--- a/backend/lib/access/users-password.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["data", "scope"],
- "properties": {
- "data": {
- "$ref": "objects#/properties/users"
- },
- "scope": {
- "type": "array",
- "contains": {
- "type": "string",
- "pattern": "^user$"
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/access/users-permissions.json b/backend/lib/access/users-permissions.json
deleted file mode 100644
index aeadc94ba..000000000
--- a/backend/lib/access/users-permissions.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- }
- ]
-}
diff --git a/backend/lib/access/users-update.json b/backend/lib/access/users-update.json
deleted file mode 100644
index 2a2f0423a..000000000
--- a/backend/lib/access/users-update.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "anyOf": [
- {
- "$ref": "roles#/definitions/admin"
- },
- {
- "type": "object",
- "required": ["data", "scope"],
- "properties": {
- "data": {
- "$ref": "objects#/properties/users"
- },
- "scope": {
- "type": "array",
- "contains": {
- "type": "string",
- "pattern": "^user$"
- }
- }
- }
- }
- ]
-}
diff --git a/backend/lib/certbot.js b/backend/lib/certbot.js
deleted file mode 100644
index 8ae3dd9c9..000000000
--- a/backend/lib/certbot.js
+++ /dev/null
@@ -1,75 +0,0 @@
-const dnsPlugins = require('../certbot-dns-plugins.json');
-const utils = require('./utils');
-const error = require('./error');
-const logger = require('../logger').certbot;
-const batchflow = require('batchflow');
-
-const certbot = {
- /**
- * @param {array} pluginKeys
- */
- installPlugins: async function (pluginKeys) {
- let hasErrors = false;
-
- return new Promise((resolve, reject) => {
- if (pluginKeys.length === 0) {
- resolve();
- return;
- }
-
- batchflow(pluginKeys)
- .sequential()
- .each((i, pluginKey, next) => {
- certbot
- .installPlugin(pluginKey)
- .then(() => {
- next();
- })
- .catch((err) => {
- hasErrors = true;
- next(err);
- });
- })
- .error((err) => {
- logger.error(err.message);
- })
- .end(() => {
- if (hasErrors) {
- reject(new error.CommandError('Some plugins failed to install. Please check the logs above', 1));
- } else {
- resolve();
- }
- });
- });
- },
-
- /**
- * Installs a cerbot plugin given the key for the object from
- * ../global/certbot-dns-plugins.json
- *
- * @param {string} pluginKey
- * @returns {Object}
- */
- installPlugin: async function (pluginKey) {
- if (typeof dnsPlugins[pluginKey] === 'undefined') {
- // throw Error(`Certbot plugin ${pluginKey} not found`);
- throw new error.ItemNotFoundError(pluginKey);
- }
-
- const plugin = dnsPlugins[pluginKey];
- logger.start(`Installing ${pluginKey}...`);
-
- const cmd = 'pip install --no-cache-dir ' + plugin.package_name;
- return utils
- .exec(cmd)
- .then((result) => {
- logger.complete(`Installed ${pluginKey}`);
- return result;
- })
- .catch((err) => {
- throw err;
- });
- },
-};
-
-module.exports = certbot;
diff --git a/backend/lib/config.js b/backend/lib/config.js
deleted file mode 100644
index e034cb482..000000000
--- a/backend/lib/config.js
+++ /dev/null
@@ -1,186 +0,0 @@
-const fs = require('fs');
-const NodeRSA = require('node-rsa');
-const logger = require('../logger').global;
-
-const keysFile = '/data/etc/npm/keys.json';
-
-let instance = null;
-
-// 1. Load from config file first (not recommended anymore)
-// 2. Use config env variables next
-const configure = () => {
- const filename = (process.env.NODE_CONFIG_DIR || '/data/etc/npm') + '/' + (process.env.NODE_ENV || 'default') + '.json';
- if (fs.existsSync(filename)) {
- let configData;
- try {
- configData = require(filename);
- } catch {
- // do nothing
- }
-
- if (configData && configData.database) {
- logger.info(`Using configuration from file: ${filename}`);
- instance = configData;
- instance.keys = getKeys();
- return;
- }
- }
-
- const envMysqlHost = process.env.DB_MYSQL_HOST || null;
- const envMysqlUser = process.env.DB_MYSQL_USER || null;
- const envMysqlName = process.env.DB_MYSQL_NAME || null;
- const envMysqlTls = process.env.DB_MYSQL_TLS || null;
- const envMysqlCa = process.env.DB_MYSQL_CA || '/etc/ssl/certs/ca-certificates.crt';
- if (envMysqlHost && envMysqlUser && envMysqlName) {
- // we have enough mysql creds to go with mysql
- logger.info('Using MySQL configuration');
- instance = {
- database: {
- engine: 'mysql2',
- host: envMysqlHost,
- port: process.env.DB_MYSQL_PORT || 3306,
- user: envMysqlUser,
- password: process.env.DB_MYSQL_PASSWORD,
- name: envMysqlName,
- ssl: envMysqlTls ? { ca: fs.readFileSync(envMysqlCa) } : false,
- },
- keys: getKeys(),
- };
- return;
- }
-
- const envSqliteFile = process.env.DB_SQLITE_FILE || '/data/etc/npm/database.sqlite';
- logger.info(`Using Sqlite: ${envSqliteFile}`);
- instance = {
- database: {
- engine: 'knex-native',
- knex: {
- client: 'better-sqlite3',
- connection: {
- filename: envSqliteFile,
- },
- useNullAsDefault: true,
- },
- },
- keys: getKeys(),
- };
-};
-
-const getKeys = () => {
- // Get keys from file
- if (!fs.existsSync(keysFile)) {
- generateKeys();
- } else if (process.env.DEBUG) {
- logger.info('Keys file exists OK');
- }
- try {
- return require(keysFile);
- } catch (err) {
- logger.error('Could not read JWT key pair from config file: ' + keysFile, err);
- process.exit(1);
- }
-};
-
-const generateKeys = () => {
- logger.info('Creating a new JWT key pair...');
- // Now create the keys and save them in the config.
- const key = new NodeRSA({ b: 2048 });
- key.generateKeyPair();
-
- const keys = {
- key: key.exportKey('private').toString(),
- pub: key.exportKey('public').toString(),
- };
-
- // Write keys config
- try {
- fs.writeFileSync(keysFile, JSON.stringify(keys, null, 2));
- } catch (err) {
- logger.error('Could not write JWT key pair to config file: ' + keysFile + ': ' + err.message);
- process.exit(1);
- }
- logger.info('Wrote JWT key pair to config file: ' + keysFile);
-};
-
-module.exports = {
- /**
- *
- * @param {string} key ie: 'database' or 'database.engine'
- * @returns {boolean}
- */
- has: function (key) {
- instance === null && configure();
- const keys = key.split('.');
- let level = instance;
- let has = true;
- keys.forEach((keyItem) => {
- if (typeof level[keyItem] === 'undefined') {
- has = false;
- } else {
- level = level[keyItem];
- }
- });
-
- return has;
- },
-
- /**
- * Gets a specific key from the top level
- *
- * @param {string} key
- * @returns {*}
- */
- get: function (key) {
- instance === null && configure();
- if (key && typeof instance[key] !== 'undefined') {
- return instance[key];
- }
- return instance;
- },
-
- /**
- * Is this a sqlite configuration?
- *
- * @returns {boolean}
- */
- isSqlite: function () {
- instance === null && configure();
- return instance.database.knex && instance.database.knex.client === 'better-sqlite3';
- },
-
- /**
- * Are we running in debug mode?
- *
- * @returns {boolean}
- */
- debug: function () {
- return !!process.env.DEBUG;
- },
-
- /**
- * Returns a public key
- *
- * @returns {string}
- */
- getPublicKey: function () {
- instance === null && configure();
- return instance.keys.pub;
- },
-
- /**
- * Returns a private key
- *
- * @returns {string}
- */
- getPrivateKey: function () {
- instance === null && configure();
- return instance.keys.key;
- },
-
- /**
- * @returns {boolean}
- */
- useLetsencryptStaging: function () {
- return !!process.env.LE_STAGING;
- },
-};
diff --git a/backend/lib/error.js b/backend/lib/error.js
deleted file mode 100644
index b78c46302..000000000
--- a/backend/lib/error.js
+++ /dev/null
@@ -1,98 +0,0 @@
-const _ = require('lodash');
-const util = require('util');
-
-module.exports = {
- PermissionError: function (message, previous) {
- Error.captureStackTrace(this, this.constructor);
- this.name = this.constructor.name;
- this.previous = previous;
- this.message = 'Permission Denied';
- this.public = true;
- this.status = 403;
- },
-
- ItemNotFoundError: function (id, previous) {
- Error.captureStackTrace(this, this.constructor);
- this.name = this.constructor.name;
- this.previous = previous;
- this.message = 'Item Not Found - ' + id;
- this.public = true;
- this.status = 404;
- },
-
- AuthError: function (message, previous) {
- Error.captureStackTrace(this, this.constructor);
- this.name = this.constructor.name;
- this.previous = previous;
- this.message = message;
- this.public = true;
- this.status = 401;
- },
-
- InternalError: function (message, previous) {
- Error.captureStackTrace(this, this.constructor);
- this.name = this.constructor.name;
- this.previous = previous;
- this.message = message;
- this.status = 500;
- this.public = false;
- },
-
- InternalValidationError: function (message, previous) {
- Error.captureStackTrace(this, this.constructor);
- this.name = this.constructor.name;
- this.previous = previous;
- this.message = message;
- this.status = 400;
- this.public = false;
- },
-
- ConfigurationError: function (message, previous) {
- Error.captureStackTrace(this, this.constructor);
- this.name = this.constructor.name;
- this.previous = previous;
- this.message = message;
- this.status = 400;
- this.public = true;
- },
-
- CacheError: function (message, previous) {
- Error.captureStackTrace(this, this.constructor);
- this.name = this.constructor.name;
- this.message = message;
- this.previous = previous;
- this.status = 500;
- this.public = false;
- },
-
- ValidationError: function (message, previous) {
- Error.captureStackTrace(this, this.constructor);
- this.name = this.constructor.name;
- this.previous = previous;
- this.message = message;
- this.public = true;
- this.status = 400;
- },
-
- AssertionFailedError: function (message, previous) {
- Error.captureStackTrace(this, this.constructor);
- this.name = this.constructor.name;
- this.previous = previous;
- this.message = message;
- this.public = false;
- this.status = 400;
- },
-
- CommandError: function (stdErr, code, previous) {
- Error.captureStackTrace(this, this.constructor);
- this.name = this.constructor.name;
- this.previous = previous;
- this.message = stdErr;
- this.code = code;
- this.public = false;
- },
-};
-
-_.forEach(module.exports, function (error) {
- util.inherits(error, Error);
-});
diff --git a/backend/lib/express/cors.js b/backend/lib/express/cors.js
deleted file mode 100644
index 60d18f855..000000000
--- a/backend/lib/express/cors.js
+++ /dev/null
@@ -1,36 +0,0 @@
-const validator = require('../validator');
-
-module.exports = function (req, res, next) {
- if (req.headers.origin) {
- const originSchema = {
- oneOf: [
- {
- type: 'string',
- pattern: '^[a-z\\-]+:\\/\\/(?:[\\w\\-\\.]+(:[0-9]+)?/?)?$',
- },
- {
- type: 'string',
- pattern: '^[a-z\\-]+:\\/\\/(?:\\[([a-z0-9]{0,4}:?)+\\])?/?(:[0-9]+)?$',
- },
- ],
- };
-
- // very relaxed validation....
- validator(originSchema, req.headers.origin)
- .then(function () {
- res.set({
- 'Access-Control-Allow-Origin': req.headers.origin,
- 'Access-Control-Allow-Credentials': true,
- 'Access-Control-Allow-Methods': 'OPTIONS, GET, POST',
- 'Access-Control-Allow-Headers': 'Content-Type, Cache-Control, Pragma, Expires, Authorization, X-Dataset-Total, X-Dataset-Offset, X-Dataset-Limit',
- 'Access-Control-Max-Age': 5 * 60,
- 'Access-Control-Expose-Headers': 'X-Dataset-Total, X-Dataset-Offset, X-Dataset-Limit',
- });
- next();
- })
- .catch(next);
- } else {
- // No origin
- next();
- }
-};
diff --git a/backend/lib/express/jwt-decode.js b/backend/lib/express/jwt-decode.js
deleted file mode 100644
index 38563d00e..000000000
--- a/backend/lib/express/jwt-decode.js
+++ /dev/null
@@ -1,15 +0,0 @@
-const Access = require('../access');
-
-module.exports = () => {
- return function (req, res, next) {
- res.locals.access = null;
- const access = new Access(res.locals.token || null);
- access
- .load()
- .then(() => {
- res.locals.access = access;
- next();
- })
- .catch(next);
- };
-};
diff --git a/backend/lib/express/jwt.js b/backend/lib/express/jwt.js
deleted file mode 100644
index adaabafa4..000000000
--- a/backend/lib/express/jwt.js
+++ /dev/null
@@ -1,13 +0,0 @@
-module.exports = function () {
- return function (req, res, next) {
- if (req.headers.authorization) {
- const parts = req.headers.authorization.split(' ');
-
- if (parts && parts[0] === 'Bearer' && parts[1]) {
- res.locals.token = parts[1];
- }
- }
-
- next();
- };
-};
diff --git a/backend/lib/express/pagination.js b/backend/lib/express/pagination.js
deleted file mode 100644
index ac01a66a1..000000000
--- a/backend/lib/express/pagination.js
+++ /dev/null
@@ -1,53 +0,0 @@
-const _ = require('lodash');
-
-module.exports = function (default_sort, default_offset, default_limit, max_limit) {
- /**
- * This will setup the req query params with filtered data and defaults
- *
- * sort will be an array of fields and their direction
- * offset will be an int, defaulting to zero if no other default supplied
- * limit will be an int, defaulting to 50 if no other default supplied, and limited to the max if that was supplied
- *
- */
-
- return function (req, res, next) {
- req.query.offset = typeof req.query.limit === 'undefined' ? default_offset || 0 : parseInt(req.query.offset, 10);
- req.query.limit = typeof req.query.limit === 'undefined' ? default_limit || 50 : parseInt(req.query.limit, 10);
-
- if (max_limit && req.query.limit > max_limit) {
- req.query.limit = max_limit;
- }
-
- // Sorting
- let sort = typeof req.query.sort === 'undefined' ? default_sort : req.query.sort;
- const myRegexp = /.*\.(asc|desc)$/gi;
- const sort_array = [];
-
- sort = sort.split(',');
- _.map(sort, function (val) {
- const matches = myRegexp.exec(val);
-
- if (matches !== null) {
- const dir = matches[1];
- sort_array.push({
- field: val.substr(0, val.length - (dir.length + 1)),
- dir: dir.toLowerCase(),
- });
- } else {
- sort_array.push({
- field: val,
- dir: 'asc',
- });
- }
- });
-
- // Sort will now be in this format:
- // [
- // { field: 'field1', dir: 'asc' },
- // { field: 'field2', dir: 'desc' }
- // ]
-
- req.query.sort = sort_array;
- next();
- };
-};
diff --git a/backend/lib/express/user-id-from-me.js b/backend/lib/express/user-id-from-me.js
deleted file mode 100644
index 4a37a4069..000000000
--- a/backend/lib/express/user-id-from-me.js
+++ /dev/null
@@ -1,9 +0,0 @@
-module.exports = (req, res, next) => {
- if (req.params.user_id === 'me' && res.locals.access) {
- req.params.user_id = res.locals.access.token.get('attrs').id;
- } else {
- req.params.user_id = parseInt(req.params.user_id, 10);
- }
-
- next();
-};
diff --git a/backend/lib/helpers.js b/backend/lib/helpers.js
deleted file mode 100644
index bb09abbaf..000000000
--- a/backend/lib/helpers.js
+++ /dev/null
@@ -1,30 +0,0 @@
-const moment = require('moment');
-
-module.exports = {
- /**
- * Takes an expression such as 30d and returns a moment object of that date in future
- *
- * Key Shorthand
- * ==================
- * years y
- * quarters Q
- * months M
- * weeks w
- * days d
- * hours h
- * minutes m
- * seconds s
- * milliseconds ms
- *
- * @param {String} expression
- * @returns {Object}
- */
- parseDatePeriod: function (expression) {
- const matches = expression.match(/^([0-9]+)(y|Q|M|w|d|h|m|s|ms)$/m);
- if (matches) {
- return moment().add(matches[1], matches[2]);
- }
-
- return null;
- },
-};
diff --git a/backend/lib/migrate_template.js b/backend/lib/migrate_template.js
deleted file mode 100644
index 21da446fe..000000000
--- a/backend/lib/migrate_template.js
+++ /dev/null
@@ -1,54 +0,0 @@
-const migrate_name = 'identifier_for_migrate';
-const logger = require('../logger').migrate;
-
-/**
- * Migrate
- *
- * @see http://knexjs.org/#Schema
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.up = function (knex, Promise) {
- logger.info('[' + migrate_name + '] Migrating Up...');
-
- // Create Table example:
-
- /* return knex.schema.createTable('notification', (table) => {
- table.increments().primary();
- table.string('name').notNull();
- table.string('type').notNull();
- table.integer('created_on').notNull();
- table.integer('modified_on').notNull();
- })
- .then(function () {
- logger.info('[' + migrate_name + '] Notification Table created');
- }); */
-
- logger.info('[' + migrate_name + '] Migrating Up Complete');
-
- return Promise.resolve(true);
-};
-
-/**
- * Undo Migrate
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.down = function (knex, Promise) {
- logger.info('[' + migrate_name + '] Migrating Down...');
-
- // Drop table example:
-
- /* return knex.schema.dropTable('notification')
- .then(() => {
- logger.info('[' + migrate_name + '] Notification Table dropped');
- }); */
-
- logger.info('[' + migrate_name + '] Migrating Down Complete');
-
- return Promise.resolve(true);
-};
diff --git a/backend/lib/utils.js b/backend/lib/utils.js
deleted file mode 100644
index 9b3983254..000000000
--- a/backend/lib/utils.js
+++ /dev/null
@@ -1,138 +0,0 @@
-const _ = require('lodash');
-const exec = require('child_process').exec;
-const spawn = require('child_process').spawn;
-const execFile = require('child_process').execFile;
-const { Liquid } = require('liquidjs');
-const error = require('./error');
-// const logger = require('../logger').global;
-
-module.exports = {
- /**
- * @param {String} cmd
- */
- exec: async function (cmd, options = {}) {
- // logger.debug('CMD:', cmd);
-
- const { stdout, stderr } = await new Promise((resolve, reject) => {
- const child = exec(cmd, options, (isError, stdout, stderr) => {
- if (isError) {
- reject(new error.CommandError(stderr, isError));
- } else {
- resolve({ stdout, stderr });
- }
- });
-
- child.on('error', (e) => {
- reject(new error.CommandError(stderr, 1, e));
- });
- });
- return stdout;
- },
-
- /**
- * @param {String} cmd
- * @param {Array} args
- */
- execFile: async function (cmd, args, options = {}) {
- // logger.debug('CMD: ' + cmd + ' ' + (args ? args.join(' ') : ''));
-
- const { stdout, stderr } = await new Promise((resolve, reject) => {
- const child = execFile(cmd, args, options, (isError, stdout, stderr) => {
- if (isError) {
- reject(new error.CommandError(stderr, isError));
- } else {
- resolve({ stdout, stderr });
- }
- });
-
- child.on('error', (e) => {
- reject(new error.CommandError(stderr, 1, e));
- });
- });
- return stdout;
- },
-
- /**
- * @param {String} cmd
- */
- execfg: function (cmd) {
- return new Promise((resolve, reject) => {
- const childProcess = spawn(cmd, {
- shell: true,
- detached: true,
- stdio: 'inherit',
- });
-
- childProcess.on('error', (err) => {
- reject(err);
- });
-
- childProcess.on('close', (code) => {
- if (code !== 0) {
- reject(new Error(`Command '${cmd}' exited with code ${code}`));
- } else {
- resolve();
- }
- });
- });
- },
-
- /**
- * Used in objection query builder
- *
- * @param {Array} omissions
- * @returns {Function}
- */
- omitRow: function (omissions) {
- /**
- * @param {Object} row
- * @returns {Object}
- */
- return (row) => {
- return _.omit(row, omissions);
- };
- },
-
- /**
- * Used in objection query builder
- *
- * @param {Array} omissions
- * @returns {Function}
- */
- omitRows: function (omissions) {
- /**
- * @param {Array} rows
- * @returns {Object}
- */
- return (rows) => {
- rows.forEach((row, idx) => {
- rows[idx] = _.omit(row, omissions);
- });
- return rows;
- };
- },
-
- /**
- * @returns {Object} Liquid render engine
- */
- getRenderEngine: function () {
- const renderEngine = new Liquid({
- root: __dirname + '/../templates/',
- });
-
- /**
- * nginxAccessRule expects the object given to have 2 properties:
- *
- * directive string
- * address string
- */
- renderEngine.registerFilter('nginxAccessRule', (v) => {
- if (typeof v.directive !== 'undefined' && typeof v.address !== 'undefined' && v.directive && v.address) {
- return `${v.directive} ${v.address};`;
- }
- return '';
- });
-
- return renderEngine;
- },
-};
diff --git a/backend/lib/validator/api.js b/backend/lib/validator/api.js
deleted file mode 100644
index 2d5c574e5..000000000
--- a/backend/lib/validator/api.js
+++ /dev/null
@@ -1,46 +0,0 @@
-const error = require('../error');
-const path = require('path');
-const parser = require('@apidevtools/json-schema-ref-parser');
-
-const Ajv = require('ajv');
-const addFormats = require('ajv-formats');
-const ajv = new Ajv({
- verbose: true,
- validateSchema: true,
- allErrors: false,
- coerceTypes: true,
- strict: false,
-});
-addFormats(ajv);
-
-/**
- * @param {Object} schema
- * @param {Object} payload
- * @returns {Promise}
- */
-function apiValidator(schema, payload /*, description */) {
- return new Promise(function Promise_apiValidator(resolve, reject) {
- if (typeof payload === 'undefined') {
- reject(new error.ValidationError('Payload is undefined'));
- }
-
- const validate = ajv.compile(schema);
- const valid = validate(payload);
-
- if (valid && !validate.errors) {
- resolve(payload);
- } else {
- const message = ajv.errorsText(validate.errors);
- const err = new error.ValidationError(message);
- err.debug = [validate.errors, payload];
- reject(err);
- }
- });
-}
-
-apiValidator.loadSchemas = parser.dereference(path.resolve('schema/index.json')).then((schema) => {
- ajv.addSchema(schema);
- return schema;
-});
-
-module.exports = apiValidator;
diff --git a/backend/lib/validator/index.js b/backend/lib/validator/index.js
deleted file mode 100644
index 5afa36420..000000000
--- a/backend/lib/validator/index.js
+++ /dev/null
@@ -1,46 +0,0 @@
-const _ = require('lodash');
-const error = require('../error');
-const definitions = require('../../schema/definitions.json');
-
-RegExp.prototype.toJSON = RegExp.prototype.toString;
-
-const Ajv = require('ajv');
-const addFormats = require('ajv-formats');
-const ajv = new Ajv({
- verbose: true,
- allErrors: true,
- coerceTypes: true,
- schemas: [definitions],
- strict: false,
-});
-addFormats(ajv);
-
-/**
- *
- * @param {Object} schema
- * @param {Object} payload
- * @returns {Promise}
- */
-function validator(schema, payload) {
- return new Promise(function (resolve, reject) {
- if (!payload) {
- reject(new error.InternalValidationError('Payload is falsy'));
- } else {
- try {
- const validate = ajv.compile(schema);
-
- const valid = validate(payload);
- if (valid && !validate.errors) {
- resolve(_.cloneDeep(payload));
- } else {
- const message = ajv.errorsText(validate.errors);
- reject(new error.InternalValidationError(message));
- }
- } catch (err) {
- reject(err);
- }
- }
- });
-}
-
-module.exports = validator;
diff --git a/backend/logger.js b/backend/logger.js
deleted file mode 100644
index 64c451c84..000000000
--- a/backend/logger.js
+++ /dev/null
@@ -1,14 +0,0 @@
-const { Signale } = require('signale');
-
-module.exports = {
- global: new Signale({ scope: 'Global ' }),
- migrate: new Signale({ scope: 'Migrate ' }),
- express: new Signale({ scope: 'Express ' }),
- access: new Signale({ scope: 'Access ' }),
- nginx: new Signale({ scope: 'Nginx ' }),
- ssl: new Signale({ scope: 'SSL ' }),
- certbot: new Signale({ scope: 'Certbot ' }),
- import: new Signale({ scope: 'Importer ' }),
- setup: new Signale({ scope: 'Setup ' }),
- ip_ranges: new Signale({ scope: 'IP Ranges' }),
-};
diff --git a/backend/migrate.js b/backend/migrate.js
deleted file mode 100644
index 773109ab4..000000000
--- a/backend/migrate.js
+++ /dev/null
@@ -1,14 +0,0 @@
-const db = require('./db');
-const logger = require('./logger').migrate;
-
-module.exports = {
- latest: function () {
- return db.migrate.currentVersion().then((version) => {
- logger.info('Current database version:', version);
- return db.migrate.latest({
- tableName: 'migrations',
- directory: 'migrations',
- });
- });
- },
-};
diff --git a/backend/migrations/20180618015850_initial.js b/backend/migrations/20180618015850_initial.js
deleted file mode 100644
index 6377c163e..000000000
--- a/backend/migrations/20180618015850_initial.js
+++ /dev/null
@@ -1,205 +0,0 @@
-const migrate_name = 'initial-schema';
-const logger = require('../logger').migrate;
-
-/**
- * Migrate
- *
- * @see http://knexjs.org/#Schema
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.up = function (knex /*, Promise */) {
- logger.info('[' + migrate_name + '] Migrating Up...');
-
- return knex.schema
- .createTable('auth', (table) => {
- table.increments().primary();
- table.dateTime('created_on').notNull();
- table.dateTime('modified_on').notNull();
- table.integer('user_id').notNull().unsigned();
- table.string('type', 30).notNull();
- table.string('secret').notNull();
- table.json('meta').notNull();
- table.integer('is_deleted').notNull().unsigned().defaultTo(0);
- })
- .then(() => {
- logger.info('[' + migrate_name + '] auth Table created');
-
- return knex.schema.createTable('user', (table) => {
- table.increments().primary();
- table.dateTime('created_on').notNull();
- table.dateTime('modified_on').notNull();
- table.integer('is_deleted').notNull().unsigned().defaultTo(0);
- table.integer('is_disabled').notNull().unsigned().defaultTo(0);
- table.string('email').notNull();
- table.string('name').notNull();
- table.string('nickname').notNull();
- table.string('avatar').notNull();
- table.json('roles').notNull();
- });
- })
- .then(() => {
- logger.info('[' + migrate_name + '] user Table created');
-
- return knex.schema.createTable('user_permission', (table) => {
- table.increments().primary();
- table.dateTime('created_on').notNull();
- table.dateTime('modified_on').notNull();
- table.integer('user_id').notNull().unsigned();
- table.string('visibility').notNull();
- table.string('proxy_hosts').notNull();
- table.string('redirection_hosts').notNull();
- table.string('dead_hosts').notNull();
- table.string('streams').notNull();
- table.string('access_lists').notNull();
- table.string('certificates').notNull();
- table.unique('user_id');
- });
- })
- .then(() => {
- logger.info('[' + migrate_name + '] user_permission Table created');
-
- return knex.schema.createTable('proxy_host', (table) => {
- table.increments().primary();
- table.dateTime('created_on').notNull();
- table.dateTime('modified_on').notNull();
- table.integer('owner_user_id').notNull().unsigned();
- table.integer('is_deleted').notNull().unsigned().defaultTo(0);
- table.json('domain_names').notNull();
- table.string('forward_ip').notNull();
- table.integer('forward_port').notNull().unsigned();
- table.integer('access_list_id').notNull().unsigned().defaultTo(0);
- table.integer('certificate_id').notNull().unsigned().defaultTo(0);
- table.integer('ssl_forced').notNull().unsigned().defaultTo(0);
- table.integer('caching_enabled').notNull().unsigned().defaultTo(0);
- table.integer('block_exploits').notNull().unsigned().defaultTo(0);
- table.text('advanced_config').notNull().defaultTo('');
- table.json('meta').notNull();
- });
- })
- .then(() => {
- logger.info('[' + migrate_name + '] proxy_host Table created');
-
- return knex.schema.createTable('redirection_host', (table) => {
- table.increments().primary();
- table.dateTime('created_on').notNull();
- table.dateTime('modified_on').notNull();
- table.integer('owner_user_id').notNull().unsigned();
- table.integer('is_deleted').notNull().unsigned().defaultTo(0);
- table.json('domain_names').notNull();
- table.string('forward_domain_name').notNull();
- table.integer('preserve_path').notNull().unsigned().defaultTo(0);
- table.integer('certificate_id').notNull().unsigned().defaultTo(0);
- table.integer('ssl_forced').notNull().unsigned().defaultTo(0);
- table.integer('block_exploits').notNull().unsigned().defaultTo(0);
- table.text('advanced_config').notNull().defaultTo('');
- table.json('meta').notNull();
- });
- })
- .then(() => {
- logger.info('[' + migrate_name + '] redirection_host Table created');
-
- return knex.schema.createTable('dead_host', (table) => {
- table.increments().primary();
- table.dateTime('created_on').notNull();
- table.dateTime('modified_on').notNull();
- table.integer('owner_user_id').notNull().unsigned();
- table.integer('is_deleted').notNull().unsigned().defaultTo(0);
- table.json('domain_names').notNull();
- table.integer('certificate_id').notNull().unsigned().defaultTo(0);
- table.integer('ssl_forced').notNull().unsigned().defaultTo(0);
- table.text('advanced_config').notNull().defaultTo('');
- table.json('meta').notNull();
- });
- })
- .then(() => {
- logger.info('[' + migrate_name + '] dead_host Table created');
-
- return knex.schema.createTable('stream', (table) => {
- table.increments().primary();
- table.dateTime('created_on').notNull();
- table.dateTime('modified_on').notNull();
- table.integer('owner_user_id').notNull().unsigned();
- table.integer('is_deleted').notNull().unsigned().defaultTo(0);
- table.integer('incoming_port').notNull().unsigned();
- table.string('forward_ip').notNull();
- table.integer('forwarding_port').notNull().unsigned();
- table.integer('tcp_forwarding').notNull().unsigned().defaultTo(0);
- table.integer('udp_forwarding').notNull().unsigned().defaultTo(0);
- table.json('meta').notNull();
- });
- })
- .then(() => {
- logger.info('[' + migrate_name + '] stream Table created');
-
- return knex.schema.createTable('access_list', (table) => {
- table.increments().primary();
- table.dateTime('created_on').notNull();
- table.dateTime('modified_on').notNull();
- table.integer('owner_user_id').notNull().unsigned();
- table.integer('is_deleted').notNull().unsigned().defaultTo(0);
- table.string('name').notNull();
- table.json('meta').notNull();
- });
- })
- .then(() => {
- logger.info('[' + migrate_name + '] access_list Table created');
-
- return knex.schema.createTable('certificate', (table) => {
- table.increments().primary();
- table.dateTime('created_on').notNull();
- table.dateTime('modified_on').notNull();
- table.integer('owner_user_id').notNull().unsigned();
- table.integer('is_deleted').notNull().unsigned().defaultTo(0);
- table.string('provider').notNull();
- table.string('nice_name').notNull().defaultTo('');
- table.json('domain_names').notNull();
- table.dateTime('expires_on').notNull();
- table.json('meta').notNull();
- });
- })
- .then(() => {
- logger.info('[' + migrate_name + '] certificate Table created');
-
- return knex.schema.createTable('access_list_auth', (table) => {
- table.increments().primary();
- table.dateTime('created_on').notNull();
- table.dateTime('modified_on').notNull();
- table.integer('access_list_id').notNull().unsigned();
- table.string('username').notNull();
- table.string('password').notNull();
- table.json('meta').notNull();
- });
- })
- .then(() => {
- logger.info('[' + migrate_name + '] access_list_auth Table created');
-
- return knex.schema.createTable('audit_log', (table) => {
- table.increments().primary();
- table.dateTime('created_on').notNull();
- table.dateTime('modified_on').notNull();
- table.integer('user_id').notNull().unsigned();
- table.string('object_type').notNull().defaultTo('');
- table.integer('object_id').notNull().unsigned().defaultTo(0);
- table.string('action').notNull();
- table.json('meta').notNull();
- });
- })
- .then(() => {
- logger.info('[' + migrate_name + '] audit_log Table created');
- });
-};
-
-/**
- * Undo Migrate
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.down = function (knex, Promise) {
- logger.warn('[' + migrate_name + "] You can't migrate down the initial data.");
- return Promise.resolve(true);
-};
diff --git a/backend/migrations/20180929054513_websockets.js b/backend/migrations/20180929054513_websockets.js
deleted file mode 100644
index 51c2d3ee3..000000000
--- a/backend/migrations/20180929054513_websockets.js
+++ /dev/null
@@ -1,35 +0,0 @@
-const migrate_name = 'websockets';
-const logger = require('../logger').migrate;
-
-/**
- * Migrate
- *
- * @see http://knexjs.org/#Schema
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.up = function (knex /*, Promise */) {
- logger.info('[' + migrate_name + '] Migrating Up...');
-
- return knex.schema
- .table('proxy_host', function (proxy_host) {
- proxy_host.integer('allow_websocket_upgrade').notNull().unsigned().defaultTo(0);
- })
- .then(() => {
- logger.info('[' + migrate_name + '] proxy_host Table altered');
- });
-};
-
-/**
- * Undo Migrate
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.down = function (knex, Promise) {
- logger.warn('[' + migrate_name + "] You can't migrate down this one.");
- return Promise.resolve(true);
-};
diff --git a/backend/migrations/20181019052346_forward_host.js b/backend/migrations/20181019052346_forward_host.js
deleted file mode 100644
index 5a7c05746..000000000
--- a/backend/migrations/20181019052346_forward_host.js
+++ /dev/null
@@ -1,35 +0,0 @@
-const migrate_name = 'forward_host';
-const logger = require('../logger').migrate;
-
-/**
- * Migrate
- *
- * @see http://knexjs.org/#Schema
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.up = function (knex /*, Promise */) {
- logger.info('[' + migrate_name + '] Migrating Up...');
-
- return knex.schema
- .table('proxy_host', function (proxy_host) {
- proxy_host.renameColumn('forward_ip', 'forward_host');
- })
- .then(() => {
- logger.info('[' + migrate_name + '] proxy_host Table altered');
- });
-};
-
-/**
- * Undo Migrate
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.down = function (knex, Promise) {
- logger.warn('[' + migrate_name + "] You can't migrate down this one.");
- return Promise.resolve(true);
-};
diff --git a/backend/migrations/20181113041458_http2_support.js b/backend/migrations/20181113041458_http2_support.js
deleted file mode 100644
index 0ec6f1243..000000000
--- a/backend/migrations/20181113041458_http2_support.js
+++ /dev/null
@@ -1,49 +0,0 @@
-const migrate_name = 'http2_support';
-const logger = require('../logger').migrate;
-
-/**
- * Migrate
- *
- * @see http://knexjs.org/#Schema
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.up = function (knex /*, Promise */) {
- logger.info('[' + migrate_name + '] Migrating Up...');
-
- return knex.schema
- .table('proxy_host', function (proxy_host) {
- proxy_host.integer('http2_support').notNull().unsigned().defaultTo(0);
- })
- .then(() => {
- logger.info('[' + migrate_name + '] proxy_host Table altered');
-
- return knex.schema.table('redirection_host', function (redirection_host) {
- redirection_host.integer('http2_support').notNull().unsigned().defaultTo(0);
- });
- })
- .then(() => {
- logger.info('[' + migrate_name + '] redirection_host Table altered');
-
- return knex.schema.table('dead_host', function (dead_host) {
- dead_host.integer('http2_support').notNull().unsigned().defaultTo(0);
- });
- })
- .then(() => {
- logger.info('[' + migrate_name + '] dead_host Table altered');
- });
-};
-
-/**
- * Undo Migrate
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.down = function (knex, Promise) {
- logger.warn('[' + migrate_name + "] You can't migrate down this one.");
- return Promise.resolve(true);
-};
diff --git a/backend/migrations/20181213013211_forward_scheme.js b/backend/migrations/20181213013211_forward_scheme.js
deleted file mode 100644
index c08345453..000000000
--- a/backend/migrations/20181213013211_forward_scheme.js
+++ /dev/null
@@ -1,35 +0,0 @@
-const migrate_name = 'forward_scheme';
-const logger = require('../logger').migrate;
-
-/**
- * Migrate
- *
- * @see http://knexjs.org/#Schema
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.up = function (knex /*, Promise */) {
- logger.info('[' + migrate_name + '] Migrating Up...');
-
- return knex.schema
- .table('proxy_host', function (proxy_host) {
- proxy_host.string('forward_scheme').notNull().defaultTo('http');
- })
- .then(() => {
- logger.info('[' + migrate_name + '] proxy_host Table altered');
- });
-};
-
-/**
- * Undo Migrate
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.down = function (knex, Promise) {
- logger.warn('[' + migrate_name + "] You can't migrate down this one.");
- return Promise.resolve(true);
-};
diff --git a/backend/migrations/20190104035154_disabled.js b/backend/migrations/20190104035154_disabled.js
deleted file mode 100644
index 6731ea9e5..000000000
--- a/backend/migrations/20190104035154_disabled.js
+++ /dev/null
@@ -1,56 +0,0 @@
-const migrate_name = 'disabled';
-const logger = require('../logger').migrate;
-
-/**
- * Migrate
- *
- * @see http://knexjs.org/#Schema
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.up = function (knex /*, Promise */) {
- logger.info('[' + migrate_name + '] Migrating Up...');
-
- return knex.schema
- .table('proxy_host', function (proxy_host) {
- proxy_host.integer('enabled').notNull().unsigned().defaultTo(1);
- })
- .then(() => {
- logger.info('[' + migrate_name + '] proxy_host Table altered');
-
- return knex.schema.table('redirection_host', function (redirection_host) {
- redirection_host.integer('enabled').notNull().unsigned().defaultTo(1);
- });
- })
- .then(() => {
- logger.info('[' + migrate_name + '] redirection_host Table altered');
-
- return knex.schema.table('dead_host', function (dead_host) {
- dead_host.integer('enabled').notNull().unsigned().defaultTo(1);
- });
- })
- .then(() => {
- logger.info('[' + migrate_name + '] dead_host Table altered');
-
- return knex.schema.table('stream', function (stream) {
- stream.integer('enabled').notNull().unsigned().defaultTo(1);
- });
- })
- .then(() => {
- logger.info('[' + migrate_name + '] stream Table altered');
- });
-};
-
-/**
- * Undo Migrate
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.down = function (knex, Promise) {
- logger.warn('[' + migrate_name + "] You can't migrate down this one.");
- return Promise.resolve(true);
-};
diff --git a/backend/migrations/20190215115310_customlocations.js b/backend/migrations/20190215115310_customlocations.js
deleted file mode 100644
index a3f2c744d..000000000
--- a/backend/migrations/20190215115310_customlocations.js
+++ /dev/null
@@ -1,36 +0,0 @@
-const migrate_name = 'custom_locations';
-const logger = require('../logger').migrate;
-
-/**
- * Migrate
- * Extends proxy_host table with locations field
- *
- * @see http://knexjs.org/#Schema
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.up = function (knex /*, Promise */) {
- logger.info('[' + migrate_name + '] Migrating Up...');
-
- return knex.schema
- .table('proxy_host', function (proxy_host) {
- proxy_host.json('locations');
- })
- .then(() => {
- logger.info('[' + migrate_name + '] proxy_host Table altered');
- });
-};
-
-/**
- * Undo Migrate
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.down = function (knex, Promise) {
- logger.warn('[' + migrate_name + "] You can't migrate down this one.");
- return Promise.resolve(true);
-};
diff --git a/backend/migrations/20190218060101_hsts.js b/backend/migrations/20190218060101_hsts.js
deleted file mode 100644
index 3f994e4c6..000000000
--- a/backend/migrations/20190218060101_hsts.js
+++ /dev/null
@@ -1,52 +0,0 @@
-const migrate_name = 'hsts';
-const logger = require('../logger').migrate;
-
-/**
- * Migrate
- *
- * @see http://knexjs.org/#Schema
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.up = function (knex /*, Promise */) {
- logger.info('[' + migrate_name + '] Migrating Up...');
-
- return knex.schema
- .table('proxy_host', function (proxy_host) {
- proxy_host.integer('hsts_enabled').notNull().unsigned().defaultTo(0);
- proxy_host.integer('hsts_subdomains').notNull().unsigned().defaultTo(0);
- })
- .then(() => {
- logger.info('[' + migrate_name + '] proxy_host Table altered');
-
- return knex.schema.table('redirection_host', function (redirection_host) {
- redirection_host.integer('hsts_enabled').notNull().unsigned().defaultTo(0);
- redirection_host.integer('hsts_subdomains').notNull().unsigned().defaultTo(0);
- });
- })
- .then(() => {
- logger.info('[' + migrate_name + '] redirection_host Table altered');
-
- return knex.schema.table('dead_host', function (dead_host) {
- dead_host.integer('hsts_enabled').notNull().unsigned().defaultTo(0);
- dead_host.integer('hsts_subdomains').notNull().unsigned().defaultTo(0);
- });
- })
- .then(() => {
- logger.info('[' + migrate_name + '] dead_host Table altered');
- });
-};
-
-/**
- * Undo Migrate
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.down = function (knex, Promise) {
- logger.warn('[' + migrate_name + "] You can't migrate down this one.");
- return Promise.resolve(true);
-};
diff --git a/backend/migrations/20190227065017_settings.js b/backend/migrations/20190227065017_settings.js
deleted file mode 100644
index 196f4c061..000000000
--- a/backend/migrations/20190227065017_settings.js
+++ /dev/null
@@ -1,39 +0,0 @@
-const migrate_name = 'settings';
-const logger = require('../logger').migrate;
-
-/**
- * Migrate
- *
- * @see http://knexjs.org/#Schema
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.up = function (knex /*, Promise */) {
- logger.info('[' + migrate_name + '] Migrating Up...');
-
- return knex.schema
- .createTable('setting', (table) => {
- table.string('id').notNull().primary();
- table.string('name', 100).notNull();
- table.string('description', 255).notNull();
- table.string('value', 255).notNull();
- table.json('meta').notNull();
- })
- .then(() => {
- logger.info('[' + migrate_name + '] setting Table created');
- });
-};
-
-/**
- * Undo Migrate
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.down = function (knex, Promise) {
- logger.warn('[' + migrate_name + "] You can't migrate down the initial data.");
- return Promise.resolve(true);
-};
diff --git a/backend/migrations/20200410143839_access_list_client.js b/backend/migrations/20200410143839_access_list_client.js
deleted file mode 100644
index a045c26bd..000000000
--- a/backend/migrations/20200410143839_access_list_client.js
+++ /dev/null
@@ -1,51 +0,0 @@
-const migrate_name = 'access_list_client';
-const logger = require('../logger').migrate;
-
-/**
- * Migrate
- *
- * @see http://knexjs.org/#Schema
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.up = function (knex /*, Promise */) {
- logger.info('[' + migrate_name + '] Migrating Up...');
-
- return knex.schema
- .createTable('access_list_client', (table) => {
- table.increments().primary();
- table.dateTime('created_on').notNull();
- table.dateTime('modified_on').notNull();
- table.integer('access_list_id').notNull().unsigned();
- table.string('address').notNull();
- table.string('directive').notNull();
- table.json('meta').notNull();
- })
- .then(function () {
- logger.info('[' + migrate_name + '] access_list_client Table created');
-
- return knex.schema.table('access_list', function (access_list) {
- access_list.integer('satify_any').notNull().defaultTo(0);
- });
- })
- .then(() => {
- logger.info('[' + migrate_name + '] access_list Table altered');
- });
-};
-
-/**
- * Undo Migrate
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.down = function (knex /*, Promise */) {
- logger.info('[' + migrate_name + '] Migrating Down...');
-
- return knex.schema.dropTable('access_list_client').then(() => {
- logger.info('[' + migrate_name + '] access_list_client Table dropped');
- });
-};
diff --git a/backend/migrations/20200410143840_access_list_client_fix.js b/backend/migrations/20200410143840_access_list_client_fix.js
deleted file mode 100644
index ff26690d7..000000000
--- a/backend/migrations/20200410143840_access_list_client_fix.js
+++ /dev/null
@@ -1,35 +0,0 @@
-const migrate_name = 'access_list_client_fix';
-const logger = require('../logger').migrate;
-
-/**
- * Migrate
- *
- * @see http://knexjs.org/#Schema
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.up = function (knex /*, Promise */) {
- logger.info('[' + migrate_name + '] Migrating Up...');
-
- return knex.schema
- .table('access_list', function (access_list) {
- access_list.renameColumn('satify_any', 'satisfy_any');
- })
- .then(() => {
- logger.info('[' + migrate_name + '] access_list Table altered');
- });
-};
-
-/**
- * Undo Migrate
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.down = function (knex, Promise) {
- logger.warn('[' + migrate_name + "] You can't migrate down this one.");
- return Promise.resolve(true);
-};
diff --git a/backend/migrations/20201014143841_pass_auth.js b/backend/migrations/20201014143841_pass_auth.js
deleted file mode 100644
index 8f222978b..000000000
--- a/backend/migrations/20201014143841_pass_auth.js
+++ /dev/null
@@ -1,42 +0,0 @@
-const migrate_name = 'pass_auth';
-const logger = require('../logger').migrate;
-
-/**
- * Migrate
- *
- * @see http://knexjs.org/#Schema
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.up = function (knex /*, Promise */) {
- logger.info('[' + migrate_name + '] Migrating Up...');
-
- return knex.schema
- .table('access_list', function (access_list) {
- access_list.integer('pass_auth').notNull().defaultTo(1);
- })
- .then(() => {
- logger.info('[' + migrate_name + '] access_list Table altered');
- });
-};
-
-/**
- * Undo Migrate
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.down = function (knex /*, Promise */) {
- logger.info('[' + migrate_name + '] Migrating Down...');
-
- return knex.schema
- .table('access_list', function (access_list) {
- access_list.dropColumn('pass_auth');
- })
- .then(() => {
- logger.info('[' + migrate_name + '] access_list pass_auth Column dropped');
- });
-};
diff --git a/backend/migrations/20210210154702_redirection_scheme.js b/backend/migrations/20210210154702_redirection_scheme.js
deleted file mode 100644
index ef9e7e962..000000000
--- a/backend/migrations/20210210154702_redirection_scheme.js
+++ /dev/null
@@ -1,42 +0,0 @@
-const migrate_name = 'redirection_scheme';
-const logger = require('../logger').migrate;
-
-/**
- * Migrate
- *
- * @see http://knexjs.org/#Schema
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.up = function (knex /*, Promise */) {
- logger.info('[' + migrate_name + '] Migrating Up...');
-
- return knex.schema
- .table('redirection_host', (table) => {
- table.string('forward_scheme').notNull().defaultTo('$scheme');
- })
- .then(function () {
- logger.info('[' + migrate_name + '] redirection_host Table altered');
- });
-};
-
-/**
- * Undo Migrate
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.down = function (knex /*, Promise */) {
- logger.info('[' + migrate_name + '] Migrating Down...');
-
- return knex.schema
- .table('redirection_host', (table) => {
- table.dropColumn('forward_scheme');
- })
- .then(function () {
- logger.info('[' + migrate_name + '] redirection_host Table altered');
- });
-};
diff --git a/backend/migrations/20210210154703_redirection_status_code.js b/backend/migrations/20210210154703_redirection_status_code.js
deleted file mode 100644
index b16d7b348..000000000
--- a/backend/migrations/20210210154703_redirection_status_code.js
+++ /dev/null
@@ -1,42 +0,0 @@
-const migrate_name = 'redirection_status_code';
-const logger = require('../logger').migrate;
-
-/**
- * Migrate
- *
- * @see http://knexjs.org/#Schema
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.up = function (knex /*, Promise */) {
- logger.info('[' + migrate_name + '] Migrating Up...');
-
- return knex.schema
- .table('redirection_host', (table) => {
- table.integer('forward_http_code').notNull().unsigned().defaultTo(302);
- })
- .then(function () {
- logger.info('[' + migrate_name + '] redirection_host Table altered');
- });
-};
-
-/**
- * Undo Migrate
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.down = function (knex /*, Promise */) {
- logger.info('[' + migrate_name + '] Migrating Down...');
-
- return knex.schema
- .table('redirection_host', (table) => {
- table.dropColumn('forward_http_code');
- })
- .then(function () {
- logger.info('[' + migrate_name + '] redirection_host Table altered');
- });
-};
diff --git a/backend/migrations/20210423103500_stream_domain.js b/backend/migrations/20210423103500_stream_domain.js
deleted file mode 100644
index 55f9c8da6..000000000
--- a/backend/migrations/20210423103500_stream_domain.js
+++ /dev/null
@@ -1,42 +0,0 @@
-const migrate_name = 'stream_domain';
-const logger = require('../logger').migrate;
-
-/**
- * Migrate
- *
- * @see http://knexjs.org/#Schema
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.up = function (knex /*, Promise */) {
- logger.info('[' + migrate_name + '] Migrating Up...');
-
- return knex.schema
- .table('stream', (table) => {
- table.renameColumn('forward_ip', 'forwarding_host');
- })
- .then(function () {
- logger.info('[' + migrate_name + '] stream Table altered');
- });
-};
-
-/**
- * Undo Migrate
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.down = function (knex /*, Promise */) {
- logger.info('[' + migrate_name + '] Migrating Down...');
-
- return knex.schema
- .table('stream', (table) => {
- table.renameColumn('forwarding_host', 'forward_ip');
- })
- .then(function () {
- logger.info('[' + migrate_name + '] stream Table altered');
- });
-};
diff --git a/backend/migrations/20211108145214_regenerate_default_host.js b/backend/migrations/20211108145214_regenerate_default_host.js
deleted file mode 100644
index 82e6c403e..000000000
--- a/backend/migrations/20211108145214_regenerate_default_host.js
+++ /dev/null
@@ -1,51 +0,0 @@
-const migrate_name = 'stream_domain';
-const logger = require('../logger').migrate;
-const internalNginx = require('../internal/nginx');
-
-async function regenerateDefaultHost(knex) {
- const row = await knex('setting').select('*').where('id', 'default-site').first();
-
- if (!row) {
- return Promise.resolve();
- }
-
- return internalNginx
- .deleteConfig('default')
- .then(() => {
- return internalNginx.generateConfig('default', row);
- })
- .then(() => {
- return internalNginx.test();
- })
- .then(() => {
- return internalNginx.reload();
- });
-}
-
-/**
- * Migrate
- *
- * @see http://knexjs.org/#Schema
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.up = function (knex) {
- logger.info('[' + migrate_name + '] Migrating Up...');
-
- return regenerateDefaultHost(knex);
-};
-
-/**
- * Undo Migrate
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.down = function (knex) {
- logger.info('[' + migrate_name + '] Migrating Down...');
-
- return regenerateDefaultHost(knex);
-};
diff --git a/backend/migrations/20240711144745_change_incoming_port_to_string.js b/backend/migrations/20240711144745_change_incoming_port_to_string.js
deleted file mode 100644
index 3d1e68661..000000000
--- a/backend/migrations/20240711144745_change_incoming_port_to_string.js
+++ /dev/null
@@ -1,42 +0,0 @@
-const migrate_name = 'change_incoming_port_to_string';
-const logger = require('../logger').migrate;
-
-/**
- * Migrate
- *
- * @see http://knexjs.org/#Schema
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.up = function (knex /*, Promise */) {
- logger.info('[' + migrate_name + '] Migrating Up...');
-
- return knex.schema
- .alterTable('stream', (table) => {
- table.string('incoming_port', 11).notNull().alter();
- })
- .then(function () {
- logger.info('[' + migrate_name + '] stream Table altered');
- });
-};
-
-/**
- * Undo Migrate
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.down = function (knex /*, Promise */) {
- logger.info('[' + migrate_name + '] Migrating Down...');
-
- return knex.schema
- .alterTable('stream', (table) => {
- table.integer('incoming_port').notNull().unsigned().alter();
- })
- .then(function () {
- logger.info('[' + migrate_name + '] stream Table altered');
- });
-};
diff --git a/backend/migrations/20240921100301_regenerate_default_host.js b/backend/migrations/20240921100301_regenerate_default_host.js
deleted file mode 100644
index 01756ab80..000000000
--- a/backend/migrations/20240921100301_regenerate_default_host.js
+++ /dev/null
@@ -1,51 +0,0 @@
-const migrate_name = 'regenerate_default_host';
-const logger = require('../logger').migrate;
-const internalNginx = require('../internal/nginx');
-
-async function regenerateDefaultHost(knex) {
- const row = await knex('setting').select('*').where('id', 'default-site').first();
-
- if (!row) {
- return Promise.resolve();
- }
-
- return internalNginx
- .deleteConfig('default')
- .then(() => {
- return internalNginx.generateConfig('default', row);
- })
- .then(() => {
- return internalNginx.test();
- })
- .then(() => {
- return internalNginx.reload();
- });
-}
-
-/**
- * Migrate
- *
- * @see http://knexjs.org/#Schema
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.up = function (knex) {
- logger.info('[' + migrate_name + '] Migrating Up...');
-
- return regenerateDefaultHost(knex);
-};
-
-/**
- * Undo Migrate
- *
- * @param {Object} knex
- * @param {Promise} Promise
- * @returns {Promise}
- */
-exports.down = function (knex) {
- logger.info('[' + migrate_name + '] Migrating Down...');
-
- return regenerateDefaultHost(knex);
-};
diff --git a/backend/models/access_list.js b/backend/models/access_list.js
deleted file mode 100644
index 3de0a96f0..000000000
--- a/backend/models/access_list.js
+++ /dev/null
@@ -1,86 +0,0 @@
-// Objection Docs:
-// http://vincit.github.io/objection.js/
-
-const db = require('../db');
-const Model = require('objection').Model;
-const User = require('./user');
-const AccessListAuth = require('./access_list_auth');
-const AccessListClient = require('./access_list_client');
-const now = require('./now_helper');
-
-Model.knex(db);
-
-class AccessList extends Model {
- $beforeInsert() {
- this.created_on = now();
- this.modified_on = now();
-
- // Default for meta
- if (typeof this.meta === 'undefined') {
- this.meta = {};
- }
- }
-
- $beforeUpdate() {
- this.modified_on = now();
- }
-
- static get name() {
- return 'AccessList';
- }
-
- static get tableName() {
- return 'access_list';
- }
-
- static get jsonAttributes() {
- return ['meta'];
- }
-
- static get relationMappings() {
- const ProxyHost = require('./proxy_host');
-
- return {
- owner: {
- relation: Model.HasOneRelation,
- modelClass: User,
- join: {
- from: 'access_list.owner_user_id',
- to: 'user.id',
- },
- modify: function (qb) {
- qb.where('user.is_deleted', 0);
- },
- },
- items: {
- relation: Model.HasManyRelation,
- modelClass: AccessListAuth,
- join: {
- from: 'access_list.id',
- to: 'access_list_auth.access_list_id',
- },
- },
- clients: {
- relation: Model.HasManyRelation,
- modelClass: AccessListClient,
- join: {
- from: 'access_list.id',
- to: 'access_list_client.access_list_id',
- },
- },
- proxy_hosts: {
- relation: Model.HasManyRelation,
- modelClass: ProxyHost,
- join: {
- from: 'access_list.id',
- to: 'proxy_host.access_list_id',
- },
- modify: function (qb) {
- qb.where('proxy_host.is_deleted', 0);
- },
- },
- };
- }
-}
-
-module.exports = AccessList;
diff --git a/backend/models/access_list_auth.js b/backend/models/access_list_auth.js
deleted file mode 100644
index 0c066f199..000000000
--- a/backend/models/access_list_auth.js
+++ /dev/null
@@ -1,54 +0,0 @@
-// Objection Docs:
-// http://vincit.github.io/objection.js/
-
-const db = require('../db');
-const Model = require('objection').Model;
-const now = require('./now_helper');
-
-Model.knex(db);
-
-class AccessListAuth extends Model {
- $beforeInsert() {
- this.created_on = now();
- this.modified_on = now();
-
- // Default for meta
- if (typeof this.meta === 'undefined') {
- this.meta = {};
- }
- }
-
- $beforeUpdate() {
- this.modified_on = now();
- }
-
- static get name() {
- return 'AccessListAuth';
- }
-
- static get tableName() {
- return 'access_list_auth';
- }
-
- static get jsonAttributes() {
- return ['meta'];
- }
-
- static get relationMappings() {
- return {
- access_list: {
- relation: Model.HasOneRelation,
- modelClass: require('./access_list'),
- join: {
- from: 'access_list_auth.access_list_id',
- to: 'access_list.id',
- },
- modify: function (qb) {
- qb.where('access_list.is_deleted', 0);
- },
- },
- };
- }
-}
-
-module.exports = AccessListAuth;
diff --git a/backend/models/access_list_client.js b/backend/models/access_list_client.js
deleted file mode 100644
index 41ad6a998..000000000
--- a/backend/models/access_list_client.js
+++ /dev/null
@@ -1,54 +0,0 @@
-// Objection Docs:
-// http://vincit.github.io/objection.js/
-
-const db = require('../db');
-const Model = require('objection').Model;
-const now = require('./now_helper');
-
-Model.knex(db);
-
-class AccessListClient extends Model {
- $beforeInsert() {
- this.created_on = now();
- this.modified_on = now();
-
- // Default for meta
- if (typeof this.meta === 'undefined') {
- this.meta = {};
- }
- }
-
- $beforeUpdate() {
- this.modified_on = now();
- }
-
- static get name() {
- return 'AccessListClient';
- }
-
- static get tableName() {
- return 'access_list_client';
- }
-
- static get jsonAttributes() {
- return ['meta'];
- }
-
- static get relationMappings() {
- return {
- access_list: {
- relation: Model.HasOneRelation,
- modelClass: require('./access_list'),
- join: {
- from: 'access_list_client.access_list_id',
- to: 'access_list.id',
- },
- modify: function (qb) {
- qb.where('access_list.is_deleted', 0);
- },
- },
- };
- }
-}
-
-module.exports = AccessListClient;
diff --git a/backend/models/audit-log.js b/backend/models/audit-log.js
deleted file mode 100644
index ec482bd84..000000000
--- a/backend/models/audit-log.js
+++ /dev/null
@@ -1,52 +0,0 @@
-// Objection Docs:
-// http://vincit.github.io/objection.js/
-
-const db = require('../db');
-const Model = require('objection').Model;
-const User = require('./user');
-const now = require('./now_helper');
-
-Model.knex(db);
-
-class AuditLog extends Model {
- $beforeInsert() {
- this.created_on = now();
- this.modified_on = now();
-
- // Default for meta
- if (typeof this.meta === 'undefined') {
- this.meta = {};
- }
- }
-
- $beforeUpdate() {
- this.modified_on = now();
- }
-
- static get name() {
- return 'AuditLog';
- }
-
- static get tableName() {
- return 'audit_log';
- }
-
- static get jsonAttributes() {
- return ['meta'];
- }
-
- static get relationMappings() {
- return {
- user: {
- relation: Model.HasOneRelation,
- modelClass: User,
- join: {
- from: 'audit_log.user_id',
- to: 'user.id',
- },
- },
- };
- }
-}
-
-module.exports = AuditLog;
diff --git a/backend/models/auth.js b/backend/models/auth.js
deleted file mode 100644
index 4c194f254..000000000
--- a/backend/models/auth.js
+++ /dev/null
@@ -1,82 +0,0 @@
-// Objection Docs:
-// http://vincit.github.io/objection.js/
-
-const bcrypt = require('bcrypt');
-const db = require('../db');
-const Model = require('objection').Model;
-const User = require('./user');
-const now = require('./now_helper');
-
-Model.knex(db);
-
-function encryptPassword() {
- /* jshint -W040 */
- const _this = this;
-
- if (_this.type === 'password' && _this.secret) {
- return bcrypt.hash(_this.secret, 13).then(function (hash) {
- _this.secret = hash;
- });
- }
-
- return null;
-}
-
-class Auth extends Model {
- $beforeInsert(queryContext) {
- this.created_on = now();
- this.modified_on = now();
-
- // Default for meta
- if (typeof this.meta === 'undefined') {
- this.meta = {};
- }
-
- return encryptPassword.apply(this, queryContext);
- }
-
- $beforeUpdate(queryContext) {
- this.modified_on = now();
- return encryptPassword.apply(this, queryContext);
- }
-
- /**
- * Verify a plain password against the encrypted password
- *
- * @param {String} password
- * @returns {Promise}
- */
- verifyPassword(password) {
- return bcrypt.compare(password, this.secret);
- }
-
- static get name() {
- return 'Auth';
- }
-
- static get tableName() {
- return 'auth';
- }
-
- static get jsonAttributes() {
- return ['meta'];
- }
-
- static get relationMappings() {
- return {
- user: {
- relation: Model.HasOneRelation,
- modelClass: User,
- join: {
- from: 'auth.user_id',
- to: 'user.id',
- },
- filter: {
- is_deleted: 0,
- },
- },
- };
- }
-}
-
-module.exports = Auth;
diff --git a/backend/models/certificate.js b/backend/models/certificate.js
deleted file mode 100644
index 6c112a183..000000000
--- a/backend/models/certificate.js
+++ /dev/null
@@ -1,65 +0,0 @@
-// Objection Docs:
-// http://vincit.github.io/objection.js/
-
-const db = require('../db');
-const Model = require('objection').Model;
-const User = require('./user');
-const now = require('./now_helper');
-
-Model.knex(db);
-
-class Certificate extends Model {
- $beforeInsert() {
- this.created_on = now();
- this.modified_on = now();
-
- // Default for expires_on
- if (typeof this.expires_on === 'undefined') {
- this.expires_on = now();
- }
-
- // Default for domain_names
- if (typeof this.domain_names === 'undefined') {
- this.domain_names = [];
- }
-
- // Default for meta
- if (typeof this.meta === 'undefined') {
- this.meta = {};
- }
- }
-
- $beforeUpdate() {
- this.modified_on = now();
- }
-
- static get name() {
- return 'Certificate';
- }
-
- static get tableName() {
- return 'certificate';
- }
-
- static get jsonAttributes() {
- return ['domain_names', 'meta'];
- }
-
- static get relationMappings() {
- return {
- owner: {
- relation: Model.HasOneRelation,
- modelClass: User,
- join: {
- from: 'certificate.owner_user_id',
- to: 'user.id',
- },
- modify: function (qb) {
- qb.where('user.is_deleted', 0);
- },
- },
- };
- }
-}
-
-module.exports = Certificate;
diff --git a/backend/models/dead_host.js b/backend/models/dead_host.js
deleted file mode 100644
index 725ebfef3..000000000
--- a/backend/models/dead_host.js
+++ /dev/null
@@ -1,72 +0,0 @@
-// Objection Docs:
-// http://vincit.github.io/objection.js/
-
-const db = require('../db');
-const Model = require('objection').Model;
-const User = require('./user');
-const Certificate = require('./certificate');
-const now = require('./now_helper');
-
-Model.knex(db);
-
-class DeadHost extends Model {
- $beforeInsert() {
- this.created_on = now();
- this.modified_on = now();
-
- // Default for domain_names
- if (typeof this.domain_names === 'undefined') {
- this.domain_names = [];
- }
-
- // Default for meta
- if (typeof this.meta === 'undefined') {
- this.meta = {};
- }
- }
-
- $beforeUpdate() {
- this.modified_on = now();
- }
-
- static get name() {
- return 'DeadHost';
- }
-
- static get tableName() {
- return 'dead_host';
- }
-
- static get jsonAttributes() {
- return ['domain_names', 'meta'];
- }
-
- static get relationMappings() {
- return {
- owner: {
- relation: Model.HasOneRelation,
- modelClass: User,
- join: {
- from: 'dead_host.owner_user_id',
- to: 'user.id',
- },
- modify: function (qb) {
- qb.where('user.is_deleted', 0);
- },
- },
- certificate: {
- relation: Model.HasOneRelation,
- modelClass: Certificate,
- join: {
- from: 'dead_host.certificate_id',
- to: 'certificate.id',
- },
- modify: function (qb) {
- qb.where('certificate.is_deleted', 0);
- },
- },
- };
- }
-}
-
-module.exports = DeadHost;
diff --git a/backend/models/now_helper.js b/backend/models/now_helper.js
deleted file mode 100644
index 99a0778be..000000000
--- a/backend/models/now_helper.js
+++ /dev/null
@@ -1,12 +0,0 @@
-const db = require('../db');
-const config = require('../lib/config');
-const Model = require('objection').Model;
-
-Model.knex(db);
-
-module.exports = function () {
- if (config.isSqlite()) {
- return Model.raw("datetime('now','localtime')");
- }
- return Model.raw('NOW()');
-};
diff --git a/backend/models/proxy_host.js b/backend/models/proxy_host.js
deleted file mode 100644
index 5959f213a..000000000
--- a/backend/models/proxy_host.js
+++ /dev/null
@@ -1,84 +0,0 @@
-// Objection Docs:
-// http://vincit.github.io/objection.js/
-
-const db = require('../db');
-const Model = require('objection').Model;
-const User = require('./user');
-const AccessList = require('./access_list');
-const Certificate = require('./certificate');
-const now = require('./now_helper');
-
-Model.knex(db);
-
-class ProxyHost extends Model {
- $beforeInsert() {
- this.created_on = now();
- this.modified_on = now();
-
- // Default for domain_names
- if (typeof this.domain_names === 'undefined') {
- this.domain_names = [];
- }
-
- // Default for meta
- if (typeof this.meta === 'undefined') {
- this.meta = {};
- }
- }
-
- $beforeUpdate() {
- this.modified_on = now();
- }
-
- static get name() {
- return 'ProxyHost';
- }
-
- static get tableName() {
- return 'proxy_host';
- }
-
- static get jsonAttributes() {
- return ['domain_names', 'meta', 'locations'];
- }
-
- static get relationMappings() {
- return {
- owner: {
- relation: Model.HasOneRelation,
- modelClass: User,
- join: {
- from: 'proxy_host.owner_user_id',
- to: 'user.id',
- },
- modify: function (qb) {
- qb.where('user.is_deleted', 0);
- },
- },
- access_list: {
- relation: Model.HasOneRelation,
- modelClass: AccessList,
- join: {
- from: 'proxy_host.access_list_id',
- to: 'access_list.id',
- },
- modify: function (qb) {
- qb.where('access_list.is_deleted', 0);
- },
- },
- certificate: {
- relation: Model.HasOneRelation,
- modelClass: Certificate,
- join: {
- from: 'proxy_host.certificate_id',
- to: 'certificate.id',
- },
- modify: function (qb) {
- qb.where('certificate.is_deleted', 0);
- },
- },
- };
- }
-}
-
-module.exports = ProxyHost;
diff --git a/backend/models/redirection_host.js b/backend/models/redirection_host.js
deleted file mode 100644
index 5018eb2a9..000000000
--- a/backend/models/redirection_host.js
+++ /dev/null
@@ -1,72 +0,0 @@
-// Objection Docs:
-// http://vincit.github.io/objection.js/
-
-const db = require('../db');
-const Model = require('objection').Model;
-const User = require('./user');
-const Certificate = require('./certificate');
-const now = require('./now_helper');
-
-Model.knex(db);
-
-class RedirectionHost extends Model {
- $beforeInsert() {
- this.created_on = now();
- this.modified_on = now();
-
- // Default for domain_names
- if (typeof this.domain_names === 'undefined') {
- this.domain_names = [];
- }
-
- // Default for meta
- if (typeof this.meta === 'undefined') {
- this.meta = {};
- }
- }
-
- $beforeUpdate() {
- this.modified_on = now();
- }
-
- static get name() {
- return 'RedirectionHost';
- }
-
- static get tableName() {
- return 'redirection_host';
- }
-
- static get jsonAttributes() {
- return ['domain_names', 'meta'];
- }
-
- static get relationMappings() {
- return {
- owner: {
- relation: Model.HasOneRelation,
- modelClass: User,
- join: {
- from: 'redirection_host.owner_user_id',
- to: 'user.id',
- },
- modify: function (qb) {
- qb.where('user.is_deleted', 0);
- },
- },
- certificate: {
- relation: Model.HasOneRelation,
- modelClass: Certificate,
- join: {
- from: 'redirection_host.certificate_id',
- to: 'certificate.id',
- },
- modify: function (qb) {
- qb.where('certificate.is_deleted', 0);
- },
- },
- };
- }
-}
-
-module.exports = RedirectionHost;
diff --git a/backend/models/setting.js b/backend/models/setting.js
deleted file mode 100644
index 4a10f157e..000000000
--- a/backend/models/setting.js
+++ /dev/null
@@ -1,30 +0,0 @@
-// Objection Docs:
-// http://vincit.github.io/objection.js/
-
-const db = require('../db');
-const Model = require('objection').Model;
-
-Model.knex(db);
-
-class Setting extends Model {
- $beforeInsert() {
- // Default for meta
- if (typeof this.meta === 'undefined') {
- this.meta = {};
- }
- }
-
- static get name() {
- return 'Setting';
- }
-
- static get tableName() {
- return 'setting';
- }
-
- static get jsonAttributes() {
- return ['meta'];
- }
-}
-
-module.exports = Setting;
diff --git a/backend/models/stream.js b/backend/models/stream.js
deleted file mode 100644
index b7899d9b3..000000000
--- a/backend/models/stream.js
+++ /dev/null
@@ -1,55 +0,0 @@
-// Objection Docs:
-// http://vincit.github.io/objection.js/
-
-const db = require('../db');
-const Model = require('objection').Model;
-const User = require('./user');
-const now = require('./now_helper');
-
-Model.knex(db);
-
-class Stream extends Model {
- $beforeInsert() {
- this.created_on = now();
- this.modified_on = now();
-
- // Default for meta
- if (typeof this.meta === 'undefined') {
- this.meta = {};
- }
- }
-
- $beforeUpdate() {
- this.modified_on = now();
- }
-
- static get name() {
- return 'Stream';
- }
-
- static get tableName() {
- return 'stream';
- }
-
- static get jsonAttributes() {
- return ['meta'];
- }
-
- static get relationMappings() {
- return {
- owner: {
- relation: Model.HasOneRelation,
- modelClass: User,
- join: {
- from: 'stream.owner_user_id',
- to: 'user.id',
- },
- modify: function (qb) {
- qb.where('user.is_deleted', 0);
- },
- },
- };
- }
-}
-
-module.exports = Stream;
diff --git a/backend/models/token.js b/backend/models/token.js
deleted file mode 100644
index 5ce20d7cd..000000000
--- a/backend/models/token.js
+++ /dev/null
@@ -1,126 +0,0 @@
-/**
- NOTE: This is not a database table, this is a model of a Token object that can be created/loaded
- and then has abilities after that.
- */
-
-const _ = require('lodash');
-const jwt = require('jsonwebtoken');
-const crypto = require('crypto');
-const config = require('../lib/config');
-const error = require('../lib/error');
-const logger = require('../logger').global;
-const ALGO = 'RS256';
-
-module.exports = function () {
- let token_data = {};
-
- const self = {
- /**
- * @param {Object} payload
- * @returns {Promise}
- */
- create: (payload) => {
- if (!config.getPrivateKey()) {
- logger.error('Private key is empty!');
- }
- // sign with RSA SHA256
- const options = {
- algorithm: ALGO,
- expiresIn: payload.expiresIn || '1d',
- };
-
- payload.jti = crypto.randomBytes(12).toString('base64').substring(-8);
-
- return new Promise((resolve, reject) => {
- jwt.sign(payload, config.getPrivateKey(), options, (err, token) => {
- if (err) {
- reject(err);
- } else {
- token_data = payload;
- resolve({
- token,
- payload,
- });
- }
- });
- });
- },
-
- /**
- * @param {String} token
- * @returns {Promise}
- */
- load: function (token) {
- if (!config.getPublicKey()) {
- logger.error('Public key is empty!');
- }
- return new Promise((resolve, reject) => {
- try {
- if (!token || token === null || token === 'null') {
- reject(new error.AuthError('Empty token'));
- } else {
- jwt.verify(token, config.getPublicKey(), { ignoreExpiration: false, algorithms: [ALGO] }, (err, result) => {
- if (err) {
- if (err.name === 'TokenExpiredError') {
- reject(new error.AuthError('Token has expired', err));
- } else {
- reject(err);
- }
- } else {
- token_data = result;
- resolve(token_data);
- }
- });
- }
- } catch (err) {
- reject(err);
- }
- });
- },
-
- /**
- * Does the token have the specified scope?
- *
- * @param {String} scope
- * @returns {Boolean}
- */
- hasScope: function (scope) {
- return typeof token_data.scope !== 'undefined' && _.indexOf(token_data.scope, scope) !== -1;
- },
-
- /**
- * @param {String} key
- * @return {*}
- */
- get: function (key) {
- if (typeof token_data[key] !== 'undefined') {
- return token_data[key];
- }
-
- return null;
- },
-
- /**
- * @param {String} key
- * @param {*} value
- */
- set: function (key, value) {
- token_data[key] = value;
- },
-
- /**
- * @param [default_value]
- * @returns {Integer}
- */
- getUserId: (default_value) => {
- const attrs = self.get('attrs');
- if (attrs && typeof attrs.id !== 'undefined' && attrs.id) {
- return attrs.id;
- }
-
- return default_value || 0;
- },
- };
-
- return self;
-};
diff --git a/backend/models/user.js b/backend/models/user.js
deleted file mode 100644
index 5c4299993..000000000
--- a/backend/models/user.js
+++ /dev/null
@@ -1,52 +0,0 @@
-// Objection Docs:
-// http://vincit.github.io/objection.js/
-
-const db = require('../db');
-const Model = require('objection').Model;
-const UserPermission = require('./user_permission');
-const now = require('./now_helper');
-
-Model.knex(db);
-
-class User extends Model {
- $beforeInsert() {
- this.created_on = now();
- this.modified_on = now();
-
- // Default for roles
- if (typeof this.roles === 'undefined') {
- this.roles = [];
- }
- }
-
- $beforeUpdate() {
- this.modified_on = now();
- }
-
- static get name() {
- return 'User';
- }
-
- static get tableName() {
- return 'user';
- }
-
- static get jsonAttributes() {
- return ['roles'];
- }
-
- static get relationMappings() {
- return {
- permissions: {
- relation: Model.HasOneRelation,
- modelClass: UserPermission,
- join: {
- from: 'user.id',
- to: 'user_permission.user_id',
- },
- },
- };
- }
-}
-
-module.exports = User;
diff --git a/backend/models/user_permission.js b/backend/models/user_permission.js
deleted file mode 100644
index e41508d46..000000000
--- a/backend/models/user_permission.js
+++ /dev/null
@@ -1,29 +0,0 @@
-// Objection Docs:
-// http://vincit.github.io/objection.js/
-
-const db = require('../db');
-const Model = require('objection').Model;
-const now = require('./now_helper');
-
-Model.knex(db);
-
-class UserPermission extends Model {
- $beforeInsert() {
- this.created_on = now();
- this.modified_on = now();
- }
-
- $beforeUpdate() {
- this.modified_on = now();
- }
-
- static get name() {
- return 'UserPermission';
- }
-
- static get tableName() {
- return 'user_permission';
- }
-}
-
-module.exports = UserPermission;
diff --git a/backend/nodemon.json b/backend/nodemon.json
deleted file mode 100644
index 3d6d13420..000000000
--- a/backend/nodemon.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "verbose": false,
- "ignore": [
- "data"
- ],
- "ext": "js json ejs"
-}
diff --git a/backend/package.json b/backend/package.json
deleted file mode 100644
index 972959f54..000000000
--- a/backend/package.json
+++ /dev/null
@@ -1,39 +0,0 @@
-{
- "name": "npmplus",
- "version": "0.0.0",
- "description": "A beautiful interface for creating Nginx endpoints",
- "main": "index.js",
- "dependencies": {
- "@apidevtools/json-schema-ref-parser": "11.7.0",
- "ajv": "8.17.1",
- "archiver": "7.0.1",
- "batchflow": "0.4.0",
- "bcrypt": "5.1.1",
- "better-sqlite3": "11.3.0",
- "body-parser": "2.0.1",
- "compression": "1.7.4",
- "express": "4.21.0",
- "express-fileupload": "1.5.1",
- "gravatar": "1.8.2",
- "jsonwebtoken": "9.0.2",
- "knex": "3.1.0",
- "liquidjs": "10.17.0",
- "lodash": "4.17.21",
- "moment": "2.30.1",
- "mysql2": "3.11.3",
- "node-rsa": "1.1.1",
- "objection": "3.1.5",
- "path": "0.12.7",
- "signale": "1.4.0"
- },
- "author": "Jamie Curnow and ZoeyVid ",
- "license": "MIT",
- "devDependencies": {
- "@eslint/js": "9.11.1",
- "eslint": "9.11.1",
- "eslint-config-prettier": "9.1.0",
- "eslint-plugin-prettier": "5.2.1",
- "globals": "15.10.0",
- "prettier": "3.3.3"
- }
-}
diff --git a/backend/password-reset.js b/backend/password-reset.js
deleted file mode 100755
index 3f82036e3..000000000
--- a/backend/password-reset.js
+++ /dev/null
@@ -1,71 +0,0 @@
-#!/usr/bin/env node
-
-// based on: https://github.com/jlesage/docker-nginx-proxy-manager/blob/796734a3f9a87e0b1561b47fd418f82216359634/rootfs/opt/nginx-proxy-manager/bin/reset-password
-
-const fs = require('fs');
-const bcrypt = require('bcrypt');
-const Database = require('better-sqlite3');
-
-function usage() {
- console.log(`usage: node ${process.argv[1]} USER_EMAIL PASSWORD
-
-Reset password of a NPMplus user.
-
-Arguments:
- USER_EMAIL Email address of the user to reset the password.
- PASSWORD Optional new password of the user. If not set, password
- is set to 'changeme'.
-`);
- process.exit(1);
-}
-
-const args = process.argv.slice(2);
-
-const USER_EMAIL = args[0];
-if (!USER_EMAIL) {
- console.error('ERROR: User email address must be set.');
- usage();
-}
-
-const PASSWORD = args[1];
-if (!PASSWORD) {
- console.error('ERROR: Password must be set.');
- usage();
-}
-
-if (fs.existsSync(process.env.DB_SQLITE_FILE)) {
- bcrypt.hash(PASSWORD, 13, (err, PASSWORD_HASH) => {
- if (err) {
- console.error(err);
- process.exit(1);
- }
- const db = new Database(process.env.DB_SQLITE_FILE);
-
- try {
- const stmt = db.prepare(`
- UPDATE auth
- SET secret = ?
- WHERE EXISTS (
- SELECT *
- FROM user
- WHERE user.id = auth.user_id AND user.email = ?
- )
- `);
-
- const result = stmt.run(PASSWORD_HASH, USER_EMAIL);
-
- if (result.changes > 0) {
- console.log(`Password for user ${USER_EMAIL} has been reset.`);
- } else {
- console.log(`No user found with email ${USER_EMAIL}.`);
- }
- } catch (error) {
- console.error(error);
- process.exit(1);
- } finally {
- db.close();
- }
-
- process.exit(0);
- });
-}
diff --git a/backend/routes/api/audit-log.js b/backend/routes/api/audit-log.js
deleted file mode 100644
index 02d8811e7..000000000
--- a/backend/routes/api/audit-log.js
+++ /dev/null
@@ -1,54 +0,0 @@
-const express = require('express');
-const validator = require('../../lib/validator');
-const jwtdecode = require('../../lib/express/jwt-decode');
-const internalAuditLog = require('../../internal/audit-log');
-
-const router = express.Router({
- caseSensitive: true,
- strict: true,
- mergeParams: true,
-});
-
-/**
- * /api/audit-log
- */
-router
- .route('/')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * GET /api/audit-log
- *
- * Retrieve all logs
- */
- .get((req, res, next) => {
- validator(
- {
- additionalProperties: false,
- properties: {
- expand: {
- $ref: 'definitions#/definitions/expand',
- },
- query: {
- $ref: 'definitions#/definitions/query',
- },
- },
- },
- {
- expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
- query: typeof req.query.query === 'string' ? req.query.query : null,
- },
- )
- .then((data) => {
- return internalAuditLog.getAll(res.locals.access, data.expand, data.query);
- })
- .then((rows) => {
- res.status(200).send(rows);
- })
- .catch(next);
- });
-
-module.exports = router;
diff --git a/backend/routes/api/main.js b/backend/routes/api/main.js
deleted file mode 100644
index 25707ba1b..000000000
--- a/backend/routes/api/main.js
+++ /dev/null
@@ -1,51 +0,0 @@
-const express = require('express');
-const pjson = require('../../package.json');
-const error = require('../../lib/error');
-
-const router = express.Router({
- caseSensitive: true,
- strict: true,
- mergeParams: true,
-});
-
-/**
- * Health Check
- * GET /api
- */
-router.get('/', (req, res /*, next */) => {
- const version = pjson.version.split('-').shift().split('.');
-
- res.status(200).send({
- status: 'OK',
- version: {
- major: parseInt(version.shift(), 10),
- minor: parseInt(version.shift(), 10),
- revision: parseInt(version.shift(), 10),
- },
- });
-});
-
-router.use('/schema', require('./schema'));
-router.use('/tokens', require('./tokens'));
-router.use('/users', require('./users'));
-router.use('/audit-log', require('./audit-log'));
-router.use('/reports', require('./reports'));
-router.use('/settings', require('./settings'));
-router.use('/nginx/proxy-hosts', require('./nginx/proxy_hosts'));
-router.use('/nginx/redirection-hosts', require('./nginx/redirection_hosts'));
-router.use('/nginx/dead-hosts', require('./nginx/dead_hosts'));
-router.use('/nginx/streams', require('./nginx/streams'));
-router.use('/nginx/access-lists', require('./nginx/access_lists'));
-router.use('/nginx/certificates', require('./nginx/certificates'));
-
-/**
- * API 404 for all other routes
- *
- * ALL /api/*
- */
-router.all(/(.+)/, function (req, res, next) {
- req.params.page = req.params['0'];
- next(new error.ItemNotFoundError(req.params.page));
-});
-
-module.exports = router;
diff --git a/backend/routes/api/nginx/access_lists.js b/backend/routes/api/nginx/access_lists.js
deleted file mode 100644
index fb09e5dbf..000000000
--- a/backend/routes/api/nginx/access_lists.js
+++ /dev/null
@@ -1,150 +0,0 @@
-const express = require('express');
-const validator = require('../../../lib/validator');
-const jwtdecode = require('../../../lib/express/jwt-decode');
-const internalAccessList = require('../../../internal/access-list');
-const apiValidator = require('../../../lib/validator/api');
-
-const router = express.Router({
- caseSensitive: true,
- strict: true,
- mergeParams: true,
-});
-
-/**
- * /api/nginx/access-lists
- */
-router
- .route('/')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * GET /api/nginx/access-lists
- *
- * Retrieve all access-lists
- */
- .get((req, res, next) => {
- validator(
- {
- additionalProperties: false,
- properties: {
- expand: {
- $ref: 'definitions#/definitions/expand',
- },
- query: {
- $ref: 'definitions#/definitions/query',
- },
- },
- },
- {
- expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
- query: typeof req.query.query === 'string' ? req.query.query : null,
- },
- )
- .then((data) => {
- return internalAccessList.getAll(res.locals.access, data.expand, data.query);
- })
- .then((rows) => {
- res.status(200).send(rows);
- })
- .catch(next);
- })
-
- /**
- * POST /api/nginx/access-lists
- *
- * Create a new access-list
- */
- .post((req, res, next) => {
- apiValidator({ $ref: 'endpoints/access-lists#/links/1/schema' }, req.body)
- .then((payload) => {
- return internalAccessList.create(res.locals.access, payload);
- })
- .then((result) => {
- res.status(201).send(result);
- })
- .catch(next);
- });
-
-/**
- * Specific access-list
- *
- * /api/nginx/access-lists/123
- */
-router
- .route('/:list_id')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * GET /api/nginx/access-lists/123
- *
- * Retrieve a specific access-list
- */
- .get((req, res, next) => {
- validator(
- {
- required: ['list_id'],
- additionalProperties: false,
- properties: {
- list_id: {
- $ref: 'definitions#/definitions/id',
- },
- expand: {
- $ref: 'definitions#/definitions/expand',
- },
- },
- },
- {
- list_id: req.params.list_id,
- expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
- },
- )
- .then((data) => {
- return internalAccessList.get(res.locals.access, {
- id: parseInt(data.list_id, 10),
- expand: data.expand,
- });
- })
- .then((row) => {
- res.status(200).send(row);
- })
- .catch(next);
- })
-
- /**
- * PUT /api/nginx/access-lists/123
- *
- * Update and existing access-list
- */
- .put((req, res, next) => {
- apiValidator({ $ref: 'endpoints/access-lists#/links/2/schema' }, req.body)
- .then((payload) => {
- payload.id = parseInt(req.params.list_id, 10);
- return internalAccessList.update(res.locals.access, payload);
- })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- })
-
- /**
- * DELETE /api/nginx/access-lists/123
- *
- * Delete and existing access-list
- */
- .delete((req, res, next) => {
- internalAccessList
- .delete(res.locals.access, { id: parseInt(req.params.list_id, 10) })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- });
-
-module.exports = router;
diff --git a/backend/routes/api/nginx/certificates.js b/backend/routes/api/nginx/certificates.js
deleted file mode 100644
index dc89152d8..000000000
--- a/backend/routes/api/nginx/certificates.js
+++ /dev/null
@@ -1,299 +0,0 @@
-const express = require('express');
-const validator = require('../../../lib/validator');
-const jwtdecode = require('../../../lib/express/jwt-decode');
-const internalCertificate = require('../../../internal/certificate');
-const apiValidator = require('../../../lib/validator/api');
-
-const router = express.Router({
- caseSensitive: true,
- strict: true,
- mergeParams: true,
-});
-
-/**
- * /api/nginx/certificates
- */
-router
- .route('/')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * GET /api/nginx/certificates
- *
- * Retrieve all certificates
- */
- .get((req, res, next) => {
- validator(
- {
- additionalProperties: false,
- properties: {
- expand: {
- $ref: 'definitions#/definitions/expand',
- },
- query: {
- $ref: 'definitions#/definitions/query',
- },
- },
- },
- {
- expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
- query: typeof req.query.query === 'string' ? req.query.query : null,
- },
- )
- .then((data) => {
- return internalCertificate.getAll(res.locals.access, data.expand, data.query);
- })
- .then((rows) => {
- res.status(200).send(rows);
- })
- .catch(next);
- })
-
- /**
- * POST /api/nginx/certificates
- *
- * Create a new certificate
- */
- .post((req, res, next) => {
- apiValidator({ $ref: 'endpoints/certificates#/links/1/schema' }, req.body)
- .then((payload) => {
- req.setTimeout(900000); // 15 minutes timeout
- return internalCertificate.create(res.locals.access, payload);
- })
- .then((result) => {
- res.status(201).send(result);
- })
- .catch(next);
- });
-
-/**
- * Test HTTP challenge for domains
- *
- * /api/nginx/certificates/test-http
- */
-router
- .route('/test-http')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * GET /api/nginx/certificates/test-http
- *
- * Test HTTP challenge for domains
- */
- .get((req, res, next) => {
- internalCertificate
- .testHttpsChallenge(res.locals.access, JSON.parse(req.query.domains))
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- });
-
-/**
- * Specific certificate
- *
- * /api/nginx/certificates/123
- */
-router
- .route('/:certificate_id')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * GET /api/nginx/certificates/123
- *
- * Retrieve a specific certificate
- */
- .get((req, res, next) => {
- validator(
- {
- required: ['certificate_id'],
- additionalProperties: false,
- properties: {
- certificate_id: {
- $ref: 'definitions#/definitions/id',
- },
- expand: {
- $ref: 'definitions#/definitions/expand',
- },
- },
- },
- {
- certificate_id: req.params.certificate_id,
- expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
- },
- )
- .then((data) => {
- return internalCertificate.get(res.locals.access, {
- id: parseInt(data.certificate_id, 10),
- expand: data.expand,
- });
- })
- .then((row) => {
- res.status(200).send(row);
- })
- .catch(next);
- })
-
- /**
- * PUT /api/nginx/certificates/123
- *
- * Update and existing certificate
- */
- .put((req, res, next) => {
- apiValidator({ $ref: 'endpoints/certificates#/links/2/schema' }, req.body)
- .then((payload) => {
- payload.id = parseInt(req.params.certificate_id, 10);
- return internalCertificate.update(res.locals.access, payload);
- })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- })
-
- /**
- * DELETE /api/nginx/certificates/123
- *
- * Update and existing certificate
- */
- .delete((req, res, next) => {
- internalCertificate
- .delete(res.locals.access, { id: parseInt(req.params.certificate_id, 10) })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- });
-
-/**
- * Upload Certs
- *
- * /api/nginx/certificates/123/upload
- */
-router
- .route('/:certificate_id/upload')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * POST /api/nginx/certificates/123/upload
- *
- * Upload certificates
- */
- .post((req, res, next) => {
- if (!req.files) {
- res.status(400).send({ error: 'No files were uploaded' });
- } else {
- internalCertificate
- .upload(res.locals.access, {
- id: parseInt(req.params.certificate_id, 10),
- files: req.files,
- })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- }
- });
-
-/**
- * Renew Certbot Certs
- *
- * /api/nginx/certificates/123/renew
- */
-router
- .route('/:certificate_id/renew')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * POST /api/nginx/certificates/123/renew
- *
- * Renew certificate
- */
- .post((req, res, next) => {
- req.setTimeout(900000); // 15 minutes timeout
- internalCertificate
- .renew(res.locals.access, {
- id: parseInt(req.params.certificate_id, 10),
- })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- });
-
-/**
- * Download Certbot Certs
- *
- * /api/nginx/certificates/123/download
- */
-router
- .route('/:certificate_id/download')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * GET /api/nginx/certificates/123/download
- *
- * Renew certificate
- */
- .get((req, res, next) => {
- internalCertificate
- .download(res.locals.access, {
- id: parseInt(req.params.certificate_id, 10),
- })
- .then((result) => {
- res.status(200).download(result.fileName);
- })
- .catch(next);
- });
-
-/**
- * Validate Certs before saving
- *
- * /api/nginx/certificates/validate
- */
-router
- .route('/validate')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * POST /api/nginx/certificates/validate
- *
- * Validate certificates
- */
- .post((req, res, next) => {
- if (!req.files) {
- res.status(400).send({ error: 'No files were uploaded' });
- } else {
- internalCertificate
- .validate({
- files: req.files,
- })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- }
- });
-
-module.exports = router;
diff --git a/backend/routes/api/nginx/dead_hosts.js b/backend/routes/api/nginx/dead_hosts.js
deleted file mode 100644
index 43f406dc1..000000000
--- a/backend/routes/api/nginx/dead_hosts.js
+++ /dev/null
@@ -1,198 +0,0 @@
-const express = require('express');
-const validator = require('../../../lib/validator');
-const jwtdecode = require('../../../lib/express/jwt-decode');
-const internalDeadHost = require('../../../internal/dead-host');
-const apiValidator = require('../../../lib/validator/api');
-
-const router = express.Router({
- caseSensitive: true,
- strict: true,
- mergeParams: true,
-});
-
-/**
- * /api/nginx/dead-hosts
- */
-router
- .route('/')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * GET /api/nginx/dead-hosts
- *
- * Retrieve all dead-hosts
- */
- .get((req, res, next) => {
- validator(
- {
- additionalProperties: false,
- properties: {
- expand: {
- $ref: 'definitions#/definitions/expand',
- },
- query: {
- $ref: 'definitions#/definitions/query',
- },
- },
- },
- {
- expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
- query: typeof req.query.query === 'string' ? req.query.query : null,
- },
- )
- .then((data) => {
- return internalDeadHost.getAll(res.locals.access, data.expand, data.query);
- })
- .then((rows) => {
- res.status(200).send(rows);
- })
- .catch(next);
- })
-
- /**
- * POST /api/nginx/dead-hosts
- *
- * Create a new dead-host
- */
- .post((req, res, next) => {
- apiValidator({ $ref: 'endpoints/dead-hosts#/links/1/schema' }, req.body)
- .then((payload) => {
- return internalDeadHost.create(res.locals.access, payload);
- })
- .then((result) => {
- res.status(201).send(result);
- })
- .catch(next);
- });
-
-/**
- * Specific dead-host
- *
- * /api/nginx/dead-hosts/123
- */
-router
- .route('/:host_id')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * GET /api/nginx/dead-hosts/123
- *
- * Retrieve a specific dead-host
- */
- .get((req, res, next) => {
- validator(
- {
- required: ['host_id'],
- additionalProperties: false,
- properties: {
- host_id: {
- $ref: 'definitions#/definitions/id',
- },
- expand: {
- $ref: 'definitions#/definitions/expand',
- },
- },
- },
- {
- host_id: req.params.host_id,
- expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
- },
- )
- .then((data) => {
- return internalDeadHost.get(res.locals.access, {
- id: parseInt(data.host_id, 10),
- expand: data.expand,
- });
- })
- .then((row) => {
- res.status(200).send(row);
- })
- .catch(next);
- })
-
- /**
- * PUT /api/nginx/dead-hosts/123
- *
- * Update and existing dead-host
- */
- .put((req, res, next) => {
- apiValidator({ $ref: 'endpoints/dead-hosts#/links/2/schema' }, req.body)
- .then((payload) => {
- payload.id = parseInt(req.params.host_id, 10);
- return internalDeadHost.update(res.locals.access, payload);
- })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- })
-
- /**
- * DELETE /api/nginx/dead-hosts/123
- *
- * Update and existing dead-host
- */
- .delete((req, res, next) => {
- internalDeadHost
- .delete(res.locals.access, { id: parseInt(req.params.host_id, 10) })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- });
-
-/**
- * Enable dead-host
- *
- * /api/nginx/dead-hosts/123/enable
- */
-router
- .route('/:host_id/enable')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * POST /api/nginx/dead-hosts/123/enable
- */
- .post((req, res, next) => {
- internalDeadHost
- .enable(res.locals.access, { id: parseInt(req.params.host_id, 10) })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- });
-
-/**
- * Disable dead-host
- *
- * /api/nginx/dead-hosts/123/disable
- */
-router
- .route('/:host_id/disable')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * POST /api/nginx/dead-hosts/123/disable
- */
- .post((req, res, next) => {
- internalDeadHost
- .disable(res.locals.access, { id: parseInt(req.params.host_id, 10) })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- });
-
-module.exports = router;
diff --git a/backend/routes/api/nginx/proxy_hosts.js b/backend/routes/api/nginx/proxy_hosts.js
deleted file mode 100644
index b3afa5df8..000000000
--- a/backend/routes/api/nginx/proxy_hosts.js
+++ /dev/null
@@ -1,198 +0,0 @@
-const express = require('express');
-const validator = require('../../../lib/validator');
-const jwtdecode = require('../../../lib/express/jwt-decode');
-const internalProxyHost = require('../../../internal/proxy-host');
-const apiValidator = require('../../../lib/validator/api');
-
-const router = express.Router({
- caseSensitive: true,
- strict: true,
- mergeParams: true,
-});
-
-/**
- * /api/nginx/proxy-hosts
- */
-router
- .route('/')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * GET /api/nginx/proxy-hosts
- *
- * Retrieve all proxy-hosts
- */
- .get((req, res, next) => {
- validator(
- {
- additionalProperties: false,
- properties: {
- expand: {
- $ref: 'definitions#/definitions/expand',
- },
- query: {
- $ref: 'definitions#/definitions/query',
- },
- },
- },
- {
- expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
- query: typeof req.query.query === 'string' ? req.query.query : null,
- },
- )
- .then((data) => {
- return internalProxyHost.getAll(res.locals.access, data.expand, data.query);
- })
- .then((rows) => {
- res.status(200).send(rows);
- })
- .catch(next);
- })
-
- /**
- * POST /api/nginx/proxy-hosts
- *
- * Create a new proxy-host
- */
- .post((req, res, next) => {
- apiValidator({ $ref: 'endpoints/proxy-hosts#/links/1/schema' }, req.body)
- .then((payload) => {
- return internalProxyHost.create(res.locals.access, payload);
- })
- .then((result) => {
- res.status(201).send(result);
- })
- .catch(next);
- });
-
-/**
- * Specific proxy-host
- *
- * /api/nginx/proxy-hosts/123
- */
-router
- .route('/:host_id')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * GET /api/nginx/proxy-hosts/123
- *
- * Retrieve a specific proxy-host
- */
- .get((req, res, next) => {
- validator(
- {
- required: ['host_id'],
- additionalProperties: false,
- properties: {
- host_id: {
- $ref: 'definitions#/definitions/id',
- },
- expand: {
- $ref: 'definitions#/definitions/expand',
- },
- },
- },
- {
- host_id: req.params.host_id,
- expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
- },
- )
- .then((data) => {
- return internalProxyHost.get(res.locals.access, {
- id: parseInt(data.host_id, 10),
- expand: data.expand,
- });
- })
- .then((row) => {
- res.status(200).send(row);
- })
- .catch(next);
- })
-
- /**
- * PUT /api/nginx/proxy-hosts/123
- *
- * Update and existing proxy-host
- */
- .put((req, res, next) => {
- apiValidator({ $ref: 'endpoints/proxy-hosts#/links/2/schema' }, req.body)
- .then((payload) => {
- payload.id = parseInt(req.params.host_id, 10);
- return internalProxyHost.update(res.locals.access, payload);
- })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- })
-
- /**
- * DELETE /api/nginx/proxy-hosts/123
- *
- * Update and existing proxy-host
- */
- .delete((req, res, next) => {
- internalProxyHost
- .delete(res.locals.access, { id: parseInt(req.params.host_id, 10) })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- });
-
-/**
- * Enable proxy-host
- *
- * /api/nginx/proxy-hosts/123/enable
- */
-router
- .route('/:host_id/enable')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * POST /api/nginx/proxy-hosts/123/enable
- */
- .post((req, res, next) => {
- internalProxyHost
- .enable(res.locals.access, { id: parseInt(req.params.host_id, 10) })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- });
-
-/**
- * Disable proxy-host
- *
- * /api/nginx/proxy-hosts/123/disable
- */
-router
- .route('/:host_id/disable')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * POST /api/nginx/proxy-hosts/123/disable
- */
- .post((req, res, next) => {
- internalProxyHost
- .disable(res.locals.access, { id: parseInt(req.params.host_id, 10) })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- });
-
-module.exports = router;
diff --git a/backend/routes/api/nginx/redirection_hosts.js b/backend/routes/api/nginx/redirection_hosts.js
deleted file mode 100644
index e069308c9..000000000
--- a/backend/routes/api/nginx/redirection_hosts.js
+++ /dev/null
@@ -1,198 +0,0 @@
-const express = require('express');
-const validator = require('../../../lib/validator');
-const jwtdecode = require('../../../lib/express/jwt-decode');
-const internalRedirectionHost = require('../../../internal/redirection-host');
-const apiValidator = require('../../../lib/validator/api');
-
-const router = express.Router({
- caseSensitive: true,
- strict: true,
- mergeParams: true,
-});
-
-/**
- * /api/nginx/redirection-hosts
- */
-router
- .route('/')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * GET /api/nginx/redirection-hosts
- *
- * Retrieve all redirection-hosts
- */
- .get((req, res, next) => {
- validator(
- {
- additionalProperties: false,
- properties: {
- expand: {
- $ref: 'definitions#/definitions/expand',
- },
- query: {
- $ref: 'definitions#/definitions/query',
- },
- },
- },
- {
- expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
- query: typeof req.query.query === 'string' ? req.query.query : null,
- },
- )
- .then((data) => {
- return internalRedirectionHost.getAll(res.locals.access, data.expand, data.query);
- })
- .then((rows) => {
- res.status(200).send(rows);
- })
- .catch(next);
- })
-
- /**
- * POST /api/nginx/redirection-hosts
- *
- * Create a new redirection-host
- */
- .post((req, res, next) => {
- apiValidator({ $ref: 'endpoints/redirection-hosts#/links/1/schema' }, req.body)
- .then((payload) => {
- return internalRedirectionHost.create(res.locals.access, payload);
- })
- .then((result) => {
- res.status(201).send(result);
- })
- .catch(next);
- });
-
-/**
- * Specific redirection-host
- *
- * /api/nginx/redirection-hosts/123
- */
-router
- .route('/:host_id')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * GET /api/nginx/redirection-hosts/123
- *
- * Retrieve a specific redirection-host
- */
- .get((req, res, next) => {
- validator(
- {
- required: ['host_id'],
- additionalProperties: false,
- properties: {
- host_id: {
- $ref: 'definitions#/definitions/id',
- },
- expand: {
- $ref: 'definitions#/definitions/expand',
- },
- },
- },
- {
- host_id: req.params.host_id,
- expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
- },
- )
- .then((data) => {
- return internalRedirectionHost.get(res.locals.access, {
- id: parseInt(data.host_id, 10),
- expand: data.expand,
- });
- })
- .then((row) => {
- res.status(200).send(row);
- })
- .catch(next);
- })
-
- /**
- * PUT /api/nginx/redirection-hosts/123
- *
- * Update and existing redirection-host
- */
- .put((req, res, next) => {
- apiValidator({ $ref: 'endpoints/redirection-hosts#/links/2/schema' }, req.body)
- .then((payload) => {
- payload.id = parseInt(req.params.host_id, 10);
- return internalRedirectionHost.update(res.locals.access, payload);
- })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- })
-
- /**
- * DELETE /api/nginx/redirection-hosts/123
- *
- * Update and existing redirection-host
- */
- .delete((req, res, next) => {
- internalRedirectionHost
- .delete(res.locals.access, { id: parseInt(req.params.host_id, 10) })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- });
-
-/**
- * Enable redirection-host
- *
- * /api/nginx/redirection-hosts/123/enable
- */
-router
- .route('/:host_id/enable')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * POST /api/nginx/redirection-hosts/123/enable
- */
- .post((req, res, next) => {
- internalRedirectionHost
- .enable(res.locals.access, { id: parseInt(req.params.host_id, 10) })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- });
-
-/**
- * Disable redirection-host
- *
- * /api/nginx/redirection-hosts/123/disable
- */
-router
- .route('/:host_id/disable')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * POST /api/nginx/redirection-hosts/123/disable
- */
- .post((req, res, next) => {
- internalRedirectionHost
- .disable(res.locals.access, { id: parseInt(req.params.host_id, 10) })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- });
-
-module.exports = router;
diff --git a/backend/routes/api/nginx/streams.js b/backend/routes/api/nginx/streams.js
deleted file mode 100644
index 24670306a..000000000
--- a/backend/routes/api/nginx/streams.js
+++ /dev/null
@@ -1,198 +0,0 @@
-const express = require('express');
-const validator = require('../../../lib/validator');
-const jwtdecode = require('../../../lib/express/jwt-decode');
-const internalStream = require('../../../internal/stream');
-const apiValidator = require('../../../lib/validator/api');
-
-const router = express.Router({
- caseSensitive: true,
- strict: true,
- mergeParams: true,
-});
-
-/**
- * /api/nginx/streams
- */
-router
- .route('/')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes
-
- /**
- * GET /api/nginx/streams
- *
- * Retrieve all streams
- */
- .get((req, res, next) => {
- validator(
- {
- additionalProperties: false,
- properties: {
- expand: {
- $ref: 'definitions#/definitions/expand',
- },
- query: {
- $ref: 'definitions#/definitions/query',
- },
- },
- },
- {
- expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
- query: typeof req.query.query === 'string' ? req.query.query : null,
- },
- )
- .then((data) => {
- return internalStream.getAll(res.locals.access, data.expand, data.query);
- })
- .then((rows) => {
- res.status(200).send(rows);
- })
- .catch(next);
- })
-
- /**
- * POST /api/nginx/streams
- *
- * Create a new stream
- */
- .post((req, res, next) => {
- apiValidator({ $ref: 'endpoints/streams#/links/1/schema' }, req.body)
- .then((payload) => {
- return internalStream.create(res.locals.access, payload);
- })
- .then((result) => {
- res.status(201).send(result);
- })
- .catch(next);
- });
-
-/**
- * Specific stream
- *
- * /api/nginx/streams/123
- */
-router
- .route('/:stream_id')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes
-
- /**
- * GET /api/nginx/streams/123
- *
- * Retrieve a specific stream
- */
- .get((req, res, next) => {
- validator(
- {
- required: ['stream_id'],
- additionalProperties: false,
- properties: {
- stream_id: {
- $ref: 'definitions#/definitions/id',
- },
- expand: {
- $ref: 'definitions#/definitions/expand',
- },
- },
- },
- {
- stream_id: req.params.stream_id,
- expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
- },
- )
- .then((data) => {
- return internalStream.get(res.locals.access, {
- id: parseInt(data.stream_id, 10),
- expand: data.expand,
- });
- })
- .then((row) => {
- res.status(200).send(row);
- })
- .catch(next);
- })
-
- /**
- * PUT /api/nginx/streams/123
- *
- * Update and existing stream
- */
- .put((req, res, next) => {
- apiValidator({ $ref: 'endpoints/streams#/links/2/schema' }, req.body)
- .then((payload) => {
- payload.id = parseInt(req.params.stream_id, 10);
- return internalStream.update(res.locals.access, payload);
- })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- })
-
- /**
- * DELETE /api/nginx/streams/123
- *
- * Update and existing stream
- */
- .delete((req, res, next) => {
- internalStream
- .delete(res.locals.access, { id: parseInt(req.params.stream_id, 10) })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- });
-
-/**
- * Enable stream
- *
- * /api/nginx/streams/123/enable
- */
-router
- .route('/:host_id/enable')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * POST /api/nginx/streams/123/enable
- */
- .post((req, res, next) => {
- internalStream
- .enable(res.locals.access, { id: parseInt(req.params.host_id, 10) })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- });
-
-/**
- * Disable stream
- *
- * /api/nginx/streams/123/disable
- */
-router
- .route('/:host_id/disable')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * POST /api/nginx/streams/123/disable
- */
- .post((req, res, next) => {
- internalStream
- .disable(res.locals.access, { id: parseInt(req.params.host_id, 10) })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- });
-
-module.exports = router;
diff --git a/backend/routes/api/reports.js b/backend/routes/api/reports.js
deleted file mode 100644
index 820ac1173..000000000
--- a/backend/routes/api/reports.js
+++ /dev/null
@@ -1,29 +0,0 @@
-const express = require('express');
-const jwtdecode = require('../../lib/express/jwt-decode');
-const internalReport = require('../../internal/report');
-
-const router = express.Router({
- caseSensitive: true,
- strict: true,
- mergeParams: true,
-});
-
-router
- .route('/hosts')
- .options((req, res) => {
- res.sendStatus(204);
- })
-
- /**
- * GET /reports/hosts
- */
- .get(jwtdecode(), (req, res, next) => {
- internalReport
- .getHostsReport(res.locals.access)
- .then((data) => {
- res.status(200).send(data);
- })
- .catch(next);
- });
-
-module.exports = router;
diff --git a/backend/routes/api/schema.js b/backend/routes/api/schema.js
deleted file mode 100644
index 446330174..000000000
--- a/backend/routes/api/schema.js
+++ /dev/null
@@ -1,36 +0,0 @@
-const express = require('express');
-const swaggerJSON = require('../../doc/api.swagger.json');
-const PACKAGE = require('../../package.json');
-
-const router = express.Router({
- caseSensitive: true,
- strict: true,
- mergeParams: true,
-});
-
-router
- .route('/')
- .options((req, res) => {
- res.sendStatus(204);
- })
-
- /**
- * GET /schema
- */
- .get((req, res /*, next */) => {
- let proto = req.protocol;
- if (typeof req.headers['x-forwarded-proto'] !== 'undefined' && req.headers['x-forwarded-proto']) {
- proto = req.headers['x-forwarded-proto'];
- }
-
- let origin = proto + '://' + req.hostname;
- if (typeof req.headers.origin !== 'undefined' && req.headers.origin) {
- origin = req.headers.origin;
- }
-
- swaggerJSON.info.version = PACKAGE.version;
- swaggerJSON.servers[0].url = origin + '/api';
- res.status(200).send(swaggerJSON);
- });
-
-module.exports = router;
diff --git a/backend/routes/api/settings.js b/backend/routes/api/settings.js
deleted file mode 100644
index 2b5afc18b..000000000
--- a/backend/routes/api/settings.js
+++ /dev/null
@@ -1,97 +0,0 @@
-const express = require('express');
-const validator = require('../../lib/validator');
-const jwtdecode = require('../../lib/express/jwt-decode');
-const internalSetting = require('../../internal/setting');
-const apiValidator = require('../../lib/validator/api');
-
-const router = express.Router({
- caseSensitive: true,
- strict: true,
- mergeParams: true,
-});
-
-/**
- * /api/settings
- */
-router
- .route('/')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * GET /api/settings
- *
- * Retrieve all settings
- */
- .get((req, res, next) => {
- internalSetting
- .getAll(res.locals.access)
- .then((rows) => {
- res.status(200).send(rows);
- })
- .catch(next);
- });
-
-/**
- * Specific setting
- *
- * /api/settings/something
- */
-router
- .route('/:setting_id')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * GET /settings/something
- *
- * Retrieve a specific setting
- */
- .get((req, res, next) => {
- validator(
- {
- required: ['setting_id'],
- additionalProperties: false,
- properties: {
- setting_id: {
- $ref: 'definitions#/definitions/setting_id',
- },
- },
- },
- {
- setting_id: req.params.setting_id,
- },
- )
- .then((data) => {
- return internalSetting.get(res.locals.access, {
- id: data.setting_id,
- });
- })
- .then((row) => {
- res.status(200).send(row);
- })
- .catch(next);
- })
-
- /**
- * PUT /api/settings/something
- *
- * Update and existing setting
- */
- .put((req, res, next) => {
- apiValidator({ $ref: 'endpoints/settings#/links/1/schema' }, req.body)
- .then((payload) => {
- payload.id = req.params.setting_id;
- return internalSetting.update(res.locals.access, payload);
- })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- });
-
-module.exports = router;
diff --git a/backend/routes/api/tokens.js b/backend/routes/api/tokens.js
deleted file mode 100644
index 031a6b2e6..000000000
--- a/backend/routes/api/tokens.js
+++ /dev/null
@@ -1,53 +0,0 @@
-const express = require('express');
-const jwtdecode = require('../../lib/express/jwt-decode');
-const internalToken = require('../../internal/token');
-const apiValidator = require('../../lib/validator/api');
-
-const router = express.Router({
- caseSensitive: true,
- strict: true,
- mergeParams: true,
-});
-
-router
- .route('/')
- .options((req, res) => {
- res.sendStatus(204);
- })
-
- /**
- * GET /tokens
- *
- * Get a new Token, given they already have a token they want to refresh
- * We also piggy back on to this method, allowing admins to get tokens
- * for services like Job board and Worker.
- */
- .get(jwtdecode(), (req, res, next) => {
- internalToken
- .getFreshToken(res.locals.access, {
- expiry: typeof req.query.expiry !== 'undefined' ? req.query.expiry : null,
- scope: typeof req.query.scope !== 'undefined' ? req.query.scope : null,
- })
- .then((data) => {
- res.status(200).send(data);
- })
- .catch(next);
- })
-
- /**
- * POST /tokens
- *
- * Create a new Token
- */
- .post((req, res, next) => {
- apiValidator({ $ref: 'endpoints/tokens#/links/0/schema' }, req.body)
- .then((payload) => {
- return internalToken.getTokenFromEmail(payload);
- })
- .then((data) => {
- res.status(200).send(data);
- })
- .catch(next);
- });
-
-module.exports = router;
diff --git a/backend/routes/api/users.js b/backend/routes/api/users.js
deleted file mode 100644
index fd186df11..000000000
--- a/backend/routes/api/users.js
+++ /dev/null
@@ -1,239 +0,0 @@
-const express = require('express');
-const validator = require('../../lib/validator');
-const jwtdecode = require('../../lib/express/jwt-decode');
-const userIdFromMe = require('../../lib/express/user-id-from-me');
-const internalUser = require('../../internal/user');
-const apiValidator = require('../../lib/validator/api');
-
-const router = express.Router({
- caseSensitive: true,
- strict: true,
- mergeParams: true,
-});
-
-/**
- * /api/users
- */
-router
- .route('/')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * GET /api/users
- *
- * Retrieve all users
- */
- .get((req, res, next) => {
- validator(
- {
- additionalProperties: false,
- properties: {
- expand: {
- $ref: 'definitions#/definitions/expand',
- },
- query: {
- $ref: 'definitions#/definitions/query',
- },
- },
- },
- {
- expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
- query: typeof req.query.query === 'string' ? req.query.query : null,
- },
- )
- .then((data) => {
- return internalUser.getAll(res.locals.access, data.expand, data.query);
- })
- .then((users) => {
- res.status(200).send(users);
- })
- .catch(next);
- })
-
- /**
- * POST /api/users
- *
- * Create a new User
- */
- .post((req, res, next) => {
- apiValidator({ $ref: 'endpoints/users#/links/1/schema' }, req.body)
- .then((payload) => {
- return internalUser.create(res.locals.access, payload);
- })
- .then((result) => {
- res.status(201).send(result);
- })
- .catch(next);
- });
-
-/**
- * Specific user
- *
- * /api/users/123
- */
-router
- .route('/:user_id')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
- .all(userIdFromMe)
-
- /**
- * GET /users/123 or /users/me
- *
- * Retrieve a specific user
- */
- .get((req, res, next) => {
- validator(
- {
- required: ['user_id'],
- additionalProperties: false,
- properties: {
- user_id: {
- $ref: 'definitions#/definitions/id',
- },
- expand: {
- $ref: 'definitions#/definitions/expand',
- },
- },
- },
- {
- user_id: req.params.user_id,
- expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
- },
- )
- .then((data) => {
- return internalUser.get(res.locals.access, {
- id: data.user_id,
- expand: data.expand,
- omit: internalUser.getUserOmisionsByAccess(res.locals.access, data.user_id),
- });
- })
- .then((user) => {
- res.status(200).send(user);
- })
- .catch(next);
- })
-
- /**
- * PUT /api/users/123
- *
- * Update and existing user
- */
- .put((req, res, next) => {
- apiValidator({ $ref: 'endpoints/users#/links/2/schema' }, req.body)
- .then((payload) => {
- payload.id = req.params.user_id;
- return internalUser.update(res.locals.access, payload);
- })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- })
-
- /**
- * DELETE /api/users/123
- *
- * Update and existing user
- */
- .delete((req, res, next) => {
- internalUser
- .delete(res.locals.access, { id: req.params.user_id })
- .then((result) => {
- res.status(200).send(result);
- })
- .catch(next);
- });
-
-/**
- * Specific user auth
- *
- * /api/users/123/auth
- */
-router
- .route('/:user_id/auth')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
- .all(userIdFromMe)
-
- /**
- * PUT /api/users/123/auth
- *
- * Update password for a user
- */
- .put((req, res, next) => {
- apiValidator({ $ref: 'endpoints/users#/links/4/schema' }, req.body)
- .then((payload) => {
- payload.id = req.params.user_id;
- return internalUser.setPassword(res.locals.access, payload);
- })
- .then((result) => {
- res.status(201).send(result);
- })
- .catch(next);
- });
-
-/**
- * Specific user permissions
- *
- * /api/users/123/permissions
- */
-router
- .route('/:user_id/permissions')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
- .all(userIdFromMe)
-
- /**
- * PUT /api/users/123/permissions
- *
- * Set some or all permissions for a user
- */
- .put((req, res, next) => {
- apiValidator({ $ref: 'endpoints/users#/links/5/schema' }, req.body)
- .then((payload) => {
- payload.id = req.params.user_id;
- return internalUser.setPermissions(res.locals.access, payload);
- })
- .then((result) => {
- res.status(201).send(result);
- })
- .catch(next);
- });
-
-/**
- * Specific user login as
- *
- * /api/users/123/login
- */
-router
- .route('/:user_id/login')
- .options((req, res) => {
- res.sendStatus(204);
- })
- .all(jwtdecode())
-
- /**
- * POST /api/users/123/login
- *
- * Log in as a user
- */
- .post((req, res, next) => {
- internalUser
- .loginAs(res.locals.access, { id: parseInt(req.params.user_id, 10) })
- .then((result) => {
- res.status(201).send(result);
- })
- .catch(next);
- });
-
-module.exports = router;
diff --git a/backend/schema/definitions.json b/backend/schema/definitions.json
deleted file mode 100644
index 7f5b4dd20..000000000
--- a/backend/schema/definitions.json
+++ /dev/null
@@ -1,240 +0,0 @@
-{
- "$schema": "http://json-schema.org/draft-07/schema#",
- "$id": "definitions",
- "definitions": {
- "id": {
- "description": "Unique identifier",
- "example": 123456,
- "readOnly": true,
- "type": "integer",
- "minimum": 1
- },
- "setting_id": {
- "description": "Unique identifier for a Setting",
- "example": "default-site",
- "readOnly": true,
- "type": "string",
- "minLength": 2
- },
- "token": {
- "type": "string",
- "minLength": 10
- },
- "expand": {
- "anyOf": [
- {
- "type": "null"
- },
- {
- "type": "array",
- "minItems": 1,
- "items": {
- "type": "string"
- }
- }
- ]
- },
- "sort": {
- "type": "array",
- "minItems": 1,
- "items": {
- "type": "object",
- "required": [
- "field",
- "dir"
- ],
- "additionalProperties": false,
- "properties": {
- "field": {
- "type": "string"
- },
- "dir": {
- "type": "string",
- "pattern": "^(asc|desc)$"
- }
- }
- }
- },
- "query": {
- "anyOf": [
- {
- "type": "null"
- },
- {
- "type": "string",
- "minLength": 1,
- "maxLength": 255
- }
- ]
- },
- "criteria": {
- "anyOf": [
- {
- "type": "null"
- },
- {
- "type": "object"
- }
- ]
- },
- "fields": {
- "anyOf": [
- {
- "type": "null"
- },
- {
- "type": "array",
- "minItems": 1,
- "items": {
- "type": "string"
- }
- }
- ]
- },
- "omit": {
- "anyOf": [
- {
- "type": "null"
- },
- {
- "type": "array",
- "minItems": 1,
- "items": {
- "type": "string"
- }
- }
- ]
- },
- "created_on": {
- "description": "Date and time of creation",
- "format": "date-time",
- "readOnly": true,
- "type": "string"
- },
- "modified_on": {
- "description": "Date and time of last update",
- "format": "date-time",
- "readOnly": true,
- "type": "string"
- },
- "user_id": {
- "description": "User ID",
- "example": 1234,
- "type": "integer",
- "minimum": 1
- },
- "certificate_id": {
- "description": "Certificate ID",
- "example": 1234,
- "anyOf": [
- {
- "type": "integer",
- "minimum": 0
- },
- {
- "type": "string",
- "pattern": "^new$"
- }
- ]
- },
- "access_list_id": {
- "description": "Access List ID",
- "example": 1234,
- "type": "integer",
- "minimum": 0
- },
- "name": {
- "type": "string",
- "minLength": 1,
- "maxLength": 255
- },
- "email": {
- "description": "Email Address",
- "example": "john@example.com",
- "format": "email",
- "type": "string",
- "minLength": 6,
- "maxLength": 100
- },
- "password": {
- "description": "Password",
- "type": "string",
- "minLength": 8,
- "maxLength": 255
- },
- "domain_name": {
- "description": "Domain Name",
- "example": "jc21.com",
- "type": "string",
- "pattern": "^(?:[^.*]+\\.?)+[^.]$"
- },
- "domain_names": {
- "description": "Domain Names separated by a comma",
- "example": "*.jc21.com,blog.jc21.com",
- "type": "array",
- "maxItems": 99,
- "uniqueItems": true,
- "items": {
- "type": "string",
- "pattern": "^(?:\\*\\.)?(?:[^.*]+\\.?)+[^.]$"
- }
- },
- "http_code": {
- "description": "Redirect HTTP Status Code",
- "example": 302,
- "type": "integer",
- "minimum": 300,
- "maximum": 308
- },
- "scheme": {
- "description": "RFC Protocol",
- "example": "HTTPS or $scheme",
- "type": "string",
- "minLength": 4
- },
- "enabled": {
- "description": "Is Enabled",
- "example": true,
- "type": "boolean"
- },
- "ssl_enabled": {
- "description": "Is SSL Enabled",
- "example": true,
- "type": "boolean"
- },
- "ssl_forced": {
- "description": "Is SSL Forced",
- "example": false,
- "type": "boolean"
- },
- "hsts_enabled": {
- "description": "Is HSTS Enabled",
- "example": false,
- "type": "boolean"
- },
- "hsts_subdomains": {
- "description": "Is HSTS applicable to all subdomains",
- "example": false,
- "type": "boolean"
- },
- "ssl_provider": {
- "type": "string",
- "pattern": "^(letsencrypt|other)$"
- },
- "http2_support": {
- "description": "HTTP2 Protocol Support",
- "example": false,
- "type": "boolean"
- },
- "block_exploits": {
- "description": "Should we block common exploits",
- "example": true,
- "type": "boolean"
- },
- "caching_enabled": {
- "description": "Should we cache assets",
- "example": true,
- "type": "boolean"
- }
- }
-}
diff --git a/backend/schema/endpoints/access-lists.json b/backend/schema/endpoints/access-lists.json
deleted file mode 100644
index 404e32376..000000000
--- a/backend/schema/endpoints/access-lists.json
+++ /dev/null
@@ -1,236 +0,0 @@
-{
- "$schema": "http://json-schema.org/draft-07/schema#",
- "$id": "endpoints/access-lists",
- "title": "Access Lists",
- "description": "Endpoints relating to Access Lists",
- "stability": "stable",
- "type": "object",
- "definitions": {
- "id": {
- "$ref": "../definitions.json#/definitions/id"
- },
- "created_on": {
- "$ref": "../definitions.json#/definitions/created_on"
- },
- "modified_on": {
- "$ref": "../definitions.json#/definitions/modified_on"
- },
- "name": {
- "type": "string",
- "description": "Name of the Access List"
- },
- "directive": {
- "type": "string",
- "enum": ["allow", "deny"]
- },
- "address": {
- "oneOf": [
- {
- "type": "string",
- "pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$"
- },
- {
- "type": "string",
- "pattern": "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$"
- },
- {
- "type": "string",
- "pattern": "^all$"
- }
- ]
- },
- "satisfy_any": {
- "type": "boolean"
- },
- "pass_auth": {
- "type": "boolean"
- },
- "meta": {
- "type": "object"
- }
- },
- "properties": {
- "id": {
- "$ref": "#/definitions/id"
- },
- "created_on": {
- "$ref": "#/definitions/created_on"
- },
- "modified_on": {
- "$ref": "#/definitions/modified_on"
- },
- "name": {
- "$ref": "#/definitions/name"
- },
- "meta": {
- "$ref": "#/definitions/meta"
- }
- },
- "links": [
- {
- "title": "List",
- "description": "Returns a list of Access Lists",
- "href": "/nginx/access-lists",
- "access": "private",
- "method": "GET",
- "rel": "self",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "targetSchema": {
- "type": "array",
- "items": {
- "$ref": "#/properties"
- }
- }
- },
- {
- "title": "Create",
- "description": "Creates a new Access List",
- "href": "/nginx/access-list",
- "access": "private",
- "method": "POST",
- "rel": "create",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "schema": {
- "type": "object",
- "additionalProperties": false,
- "required": ["name"],
- "properties": {
- "name": {
- "$ref": "#/definitions/name"
- },
- "satisfy_any": {
- "$ref": "#/definitions/satisfy_any"
- },
- "pass_auth": {
- "$ref": "#/definitions/pass_auth"
- },
- "items": {
- "type": "array",
- "minItems": 0,
- "items": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "username": {
- "type": "string",
- "minLength": 1
- },
- "password": {
- "type": "string",
- "minLength": 1
- }
- }
- }
- },
- "clients": {
- "type": "array",
- "minItems": 0,
- "items": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "address": {
- "$ref": "#/definitions/address"
- },
- "directive": {
- "$ref": "#/definitions/directive"
- }
- }
- }
- },
- "meta": {
- "$ref": "#/definitions/meta"
- }
- }
- },
- "targetSchema": {
- "properties": {
- "$ref": "#/properties"
- }
- }
- },
- {
- "title": "Update",
- "description": "Updates a existing Access List",
- "href": "/nginx/access-list/{definitions.identity.example}",
- "access": "private",
- "method": "PUT",
- "rel": "update",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "schema": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "name": {
- "$ref": "#/definitions/name"
- },
- "satisfy_any": {
- "$ref": "#/definitions/satisfy_any"
- },
- "pass_auth": {
- "$ref": "#/definitions/pass_auth"
- },
- "items": {
- "type": "array",
- "minItems": 0,
- "items": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "username": {
- "type": "string",
- "minLength": 1
- },
- "password": {
- "type": "string",
- "minLength": 0
- }
- }
- }
- },
- "clients": {
- "type": "array",
- "minItems": 0,
- "items": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "address": {
- "$ref": "#/definitions/address"
- },
- "directive": {
- "$ref": "#/definitions/directive"
- }
- }
- }
- }
- }
- },
- "targetSchema": {
- "properties": {
- "$ref": "#/properties"
- }
- }
- },
- {
- "title": "Delete",
- "description": "Deletes a existing Access List",
- "href": "/nginx/access-list/{definitions.identity.example}",
- "access": "private",
- "method": "DELETE",
- "rel": "delete",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "targetSchema": {
- "type": "boolean"
- }
- }
- ]
-}
diff --git a/backend/schema/endpoints/certificates.json b/backend/schema/endpoints/certificates.json
deleted file mode 100644
index aea3e43c7..000000000
--- a/backend/schema/endpoints/certificates.json
+++ /dev/null
@@ -1,173 +0,0 @@
-{
- "$schema": "http://json-schema.org/draft-07/schema#",
- "$id": "endpoints/certificates",
- "title": "Certificates",
- "description": "Endpoints relating to Certificates",
- "stability": "stable",
- "type": "object",
- "definitions": {
- "id": {
- "$ref": "../definitions.json#/definitions/id"
- },
- "created_on": {
- "$ref": "../definitions.json#/definitions/created_on"
- },
- "modified_on": {
- "$ref": "../definitions.json#/definitions/modified_on"
- },
- "provider": {
- "$ref": "../definitions.json#/definitions/ssl_provider"
- },
- "nice_name": {
- "type": "string",
- "description": "Nice Name for the custom certificate"
- },
- "domain_names": {
- "$ref": "../definitions.json#/definitions/domain_names"
- },
- "expires_on": {
- "description": "Date and time of expiration",
- "format": "date-time",
- "readOnly": true,
- "type": "string"
- },
- "meta": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "letsencrypt_email": {
- "type": "string",
- "format": "email"
- },
- "letsencrypt_agree": {
- "type": "boolean"
- },
- "dns_challenge": {
- "type": "boolean"
- },
- "dns_provider": {
- "type": "string"
- },
- "dns_provider_credentials": {
- "type": "string"
- },
- "propagation_seconds": {
- "anyOf": [
- {
- "type": "integer",
- "minimum": 0
- }
- ]
-
- }
- }
- }
- },
- "properties": {
- "id": {
- "$ref": "#/definitions/id"
- },
- "created_on": {
- "$ref": "#/definitions/created_on"
- },
- "modified_on": {
- "$ref": "#/definitions/modified_on"
- },
- "provider": {
- "$ref": "#/definitions/provider"
- },
- "nice_name": {
- "$ref": "#/definitions/nice_name"
- },
- "domain_names": {
- "$ref": "#/definitions/domain_names"
- },
- "expires_on": {
- "$ref": "#/definitions/expires_on"
- },
- "meta": {
- "$ref": "#/definitions/meta"
- }
- },
- "links": [
- {
- "title": "List",
- "description": "Returns a list of Certificates",
- "href": "/nginx/certificates",
- "access": "private",
- "method": "GET",
- "rel": "self",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "targetSchema": {
- "type": "array",
- "items": {
- "$ref": "#/properties"
- }
- }
- },
- {
- "title": "Create",
- "description": "Creates a new Certificate",
- "href": "/nginx/certificates",
- "access": "private",
- "method": "POST",
- "rel": "create",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "schema": {
- "type": "object",
- "additionalProperties": false,
- "required": [
- "provider"
- ],
- "properties": {
- "provider": {
- "$ref": "#/definitions/provider"
- },
- "nice_name": {
- "$ref": "#/definitions/nice_name"
- },
- "domain_names": {
- "$ref": "#/definitions/domain_names"
- },
- "meta": {
- "$ref": "#/definitions/meta"
- }
- }
- },
- "targetSchema": {
- "properties": {
- "$ref": "#/properties"
- }
- }
- },
- {
- "title": "Delete",
- "description": "Deletes a existing Certificate",
- "href": "/nginx/certificates/{definitions.identity.example}",
- "access": "private",
- "method": "DELETE",
- "rel": "delete",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "targetSchema": {
- "type": "boolean"
- }
- },
- {
- "title": "Test HTTP Challenge",
- "description": "Tests whether the HTTP challenge should work",
- "href": "/nginx/certificates/{definitions.identity.example}/test-http",
- "access": "private",
- "method": "GET",
- "rel": "info",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- }
- }
- ]
-}
diff --git a/backend/schema/endpoints/dead-hosts.json b/backend/schema/endpoints/dead-hosts.json
deleted file mode 100644
index 0c73c3be1..000000000
--- a/backend/schema/endpoints/dead-hosts.json
+++ /dev/null
@@ -1,240 +0,0 @@
-{
- "$schema": "http://json-schema.org/draft-07/schema#",
- "$id": "endpoints/dead-hosts",
- "title": "404 Hosts",
- "description": "Endpoints relating to 404 Hosts",
- "stability": "stable",
- "type": "object",
- "definitions": {
- "id": {
- "$ref": "../definitions.json#/definitions/id"
- },
- "created_on": {
- "$ref": "../definitions.json#/definitions/created_on"
- },
- "modified_on": {
- "$ref": "../definitions.json#/definitions/modified_on"
- },
- "domain_names": {
- "$ref": "../definitions.json#/definitions/domain_names"
- },
- "certificate_id": {
- "$ref": "../definitions.json#/definitions/certificate_id"
- },
- "ssl_forced": {
- "$ref": "../definitions.json#/definitions/ssl_forced"
- },
- "hsts_enabled": {
- "$ref": "../definitions.json#/definitions/hsts_enabled"
- },
- "hsts_subdomains": {
- "$ref": "../definitions.json#/definitions/hsts_subdomains"
- },
- "http2_support": {
- "$ref": "../definitions.json#/definitions/http2_support"
- },
- "advanced_config": {
- "type": "string"
- },
- "enabled": {
- "$ref": "../definitions.json#/definitions/enabled"
- },
- "meta": {
- "type": "object"
- }
- },
- "properties": {
- "id": {
- "$ref": "#/definitions/id"
- },
- "created_on": {
- "$ref": "#/definitions/created_on"
- },
- "modified_on": {
- "$ref": "#/definitions/modified_on"
- },
- "domain_names": {
- "$ref": "#/definitions/domain_names"
- },
- "certificate_id": {
- "$ref": "#/definitions/certificate_id"
- },
- "ssl_forced": {
- "$ref": "#/definitions/ssl_forced"
- },
- "hsts_enabled": {
- "$ref": "#/definitions/hsts_enabled"
- },
- "hsts_subdomains": {
- "$ref": "#/definitions/hsts_subdomains"
- },
- "http2_support": {
- "$ref": "#/definitions/http2_support"
- },
- "advanced_config": {
- "$ref": "#/definitions/advanced_config"
- },
- "enabled": {
- "$ref": "#/definitions/enabled"
- },
- "meta": {
- "$ref": "#/definitions/meta"
- }
- },
- "links": [
- {
- "title": "List",
- "description": "Returns a list of 404 Hosts",
- "href": "/nginx/dead-hosts",
- "access": "private",
- "method": "GET",
- "rel": "self",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "targetSchema": {
- "type": "array",
- "items": {
- "$ref": "#/properties"
- }
- }
- },
- {
- "title": "Create",
- "description": "Creates a new 404 Host",
- "href": "/nginx/dead-hosts",
- "access": "private",
- "method": "POST",
- "rel": "create",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "schema": {
- "type": "object",
- "additionalProperties": false,
- "required": [
- "domain_names"
- ],
- "properties": {
- "domain_names": {
- "$ref": "#/definitions/domain_names"
- },
- "certificate_id": {
- "$ref": "#/definitions/certificate_id"
- },
- "ssl_forced": {
- "$ref": "#/definitions/ssl_forced"
- },
- "hsts_enabled": {
- "$ref": "#/definitions/hsts_enabled"
- },
- "hsts_subdomains": {
- "$ref": "#/definitions/hsts_enabled"
- },
- "http2_support": {
- "$ref": "#/definitions/http2_support"
- },
- "advanced_config": {
- "$ref": "#/definitions/advanced_config"
- },
- "meta": {
- "$ref": "#/definitions/meta"
- }
- }
- },
- "targetSchema": {
- "properties": {
- "$ref": "#/properties"
- }
- }
- },
- {
- "title": "Update",
- "description": "Updates a existing 404 Host",
- "href": "/nginx/dead-hosts/{definitions.identity.example}",
- "access": "private",
- "method": "PUT",
- "rel": "update",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "schema": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "domain_names": {
- "$ref": "#/definitions/domain_names"
- },
- "certificate_id": {
- "$ref": "#/definitions/certificate_id"
- },
- "ssl_forced": {
- "$ref": "#/definitions/ssl_forced"
- },
- "hsts_enabled": {
- "$ref": "#/definitions/hsts_enabled"
- },
- "hsts_subdomains": {
- "$ref": "#/definitions/hsts_enabled"
- },
- "http2_support": {
- "$ref": "#/definitions/http2_support"
- },
- "advanced_config": {
- "$ref": "#/definitions/advanced_config"
- },
- "meta": {
- "$ref": "#/definitions/meta"
- }
- }
- },
- "targetSchema": {
- "properties": {
- "$ref": "#/properties"
- }
- }
- },
- {
- "title": "Delete",
- "description": "Deletes a existing 404 Host",
- "href": "/nginx/dead-hosts/{definitions.identity.example}",
- "access": "private",
- "method": "DELETE",
- "rel": "delete",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "targetSchema": {
- "type": "boolean"
- }
- },
- {
- "title": "Enable",
- "description": "Enables a existing 404 Host",
- "href": "/nginx/dead-hosts/{definitions.identity.example}/enable",
- "access": "private",
- "method": "POST",
- "rel": "update",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "targetSchema": {
- "type": "boolean"
- }
- },
- {
- "title": "Disable",
- "description": "Disables a existing 404 Host",
- "href": "/nginx/dead-hosts/{definitions.identity.example}/disable",
- "access": "private",
- "method": "POST",
- "rel": "update",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "targetSchema": {
- "type": "boolean"
- }
- }
- ]
-}
diff --git a/backend/schema/endpoints/proxy-hosts.json b/backend/schema/endpoints/proxy-hosts.json
deleted file mode 100644
index 9a3fff2fc..000000000
--- a/backend/schema/endpoints/proxy-hosts.json
+++ /dev/null
@@ -1,387 +0,0 @@
-{
- "$schema": "http://json-schema.org/draft-07/schema#",
- "$id": "endpoints/proxy-hosts",
- "title": "Proxy Hosts",
- "description": "Endpoints relating to Proxy Hosts",
- "stability": "stable",
- "type": "object",
- "definitions": {
- "id": {
- "$ref": "../definitions.json#/definitions/id"
- },
- "created_on": {
- "$ref": "../definitions.json#/definitions/created_on"
- },
- "modified_on": {
- "$ref": "../definitions.json#/definitions/modified_on"
- },
- "domain_names": {
- "$ref": "../definitions.json#/definitions/domain_names"
- },
- "forward_scheme": {
- "type": "string",
- "enum": ["http", "https"]
- },
- "forward_host": {
- "type": "string",
- "minLength": 1,
- "maxLength": 255
- },
- "forward_port": {
- "type": "integer",
- "minimum": 1,
- "maximum": 65535
- },
- "certificate_id": {
- "$ref": "../definitions.json#/definitions/certificate_id"
- },
- "ssl_forced": {
- "$ref": "../definitions.json#/definitions/ssl_forced"
- },
- "hsts_enabled": {
- "$ref": "../definitions.json#/definitions/hsts_enabled"
- },
- "hsts_subdomains": {
- "$ref": "../definitions.json#/definitions/hsts_subdomains"
- },
- "http2_support": {
- "$ref": "../definitions.json#/definitions/http2_support"
- },
- "block_exploits": {
- "$ref": "../definitions.json#/definitions/block_exploits"
- },
- "caching_enabled": {
- "$ref": "../definitions.json#/definitions/caching_enabled"
- },
- "allow_websocket_upgrade": {
- "description": "Allow Websocket Upgrade for all paths",
- "example": true,
- "type": "boolean"
- },
- "access_list_id": {
- "$ref": "../definitions.json#/definitions/access_list_id"
- },
- "advanced_config": {
- "type": "string"
- },
- "enabled": {
- "$ref": "../definitions.json#/definitions/enabled"
- },
- "meta": {
- "type": "object"
- },
- "locations": {
- "type": "array",
- "minItems": 0,
- "items": {
- "type": "object",
- "required": [
- "forward_scheme",
- "forward_host",
- "forward_port",
- "path"
- ],
- "additionalProperties": false,
- "properties": {
- "id": {
- "type": ["integer", "null"]
- },
- "path": {
- "type": "string",
- "minLength": 1
- },
- "forward_scheme": {
- "$ref": "#/definitions/forward_scheme"
- },
- "forward_host": {
- "$ref": "#/definitions/forward_host"
- },
- "forward_port": {
- "$ref": "#/definitions/forward_port"
- },
- "forward_path": {
- "type": "string"
- },
- "advanced_config": {
- "type": "string"
- }
- }
- }
- }
- },
- "properties": {
- "id": {
- "$ref": "#/definitions/id"
- },
- "created_on": {
- "$ref": "#/definitions/created_on"
- },
- "modified_on": {
- "$ref": "#/definitions/modified_on"
- },
- "domain_names": {
- "$ref": "#/definitions/domain_names"
- },
- "forward_scheme": {
- "$ref": "#/definitions/forward_scheme"
- },
- "forward_host": {
- "$ref": "#/definitions/forward_host"
- },
- "forward_port": {
- "$ref": "#/definitions/forward_port"
- },
- "certificate_id": {
- "$ref": "#/definitions/certificate_id"
- },
- "ssl_forced": {
- "$ref": "#/definitions/ssl_forced"
- },
- "hsts_enabled": {
- "$ref": "#/definitions/hsts_enabled"
- },
- "hsts_subdomains": {
- "$ref": "#/definitions/hsts_subdomains"
- },
- "http2_support": {
- "$ref": "#/definitions/http2_support"
- },
- "block_exploits": {
- "$ref": "#/definitions/block_exploits"
- },
- "caching_enabled": {
- "$ref": "#/definitions/caching_enabled"
- },
- "allow_websocket_upgrade": {
- "$ref": "#/definitions/allow_websocket_upgrade"
- },
- "access_list_id": {
- "$ref": "#/definitions/access_list_id"
- },
- "advanced_config": {
- "$ref": "#/definitions/advanced_config"
- },
- "enabled": {
- "$ref": "#/definitions/enabled"
- },
- "meta": {
- "$ref": "#/definitions/meta"
- },
- "locations": {
- "$ref": "#/definitions/locations"
- }
- },
- "links": [
- {
- "title": "List",
- "description": "Returns a list of Proxy Hosts",
- "href": "/nginx/proxy-hosts",
- "access": "private",
- "method": "GET",
- "rel": "self",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "targetSchema": {
- "type": "array",
- "items": {
- "$ref": "#/properties"
- }
- }
- },
- {
- "title": "Create",
- "description": "Creates a new Proxy Host",
- "href": "/nginx/proxy-hosts",
- "access": "private",
- "method": "POST",
- "rel": "create",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "schema": {
- "type": "object",
- "additionalProperties": false,
- "required": [
- "domain_names",
- "forward_scheme",
- "forward_host",
- "forward_port"
- ],
- "properties": {
- "domain_names": {
- "$ref": "#/definitions/domain_names"
- },
- "forward_scheme": {
- "$ref": "#/definitions/forward_scheme"
- },
- "forward_host": {
- "$ref": "#/definitions/forward_host"
- },
- "forward_port": {
- "$ref": "#/definitions/forward_port"
- },
- "certificate_id": {
- "$ref": "#/definitions/certificate_id"
- },
- "ssl_forced": {
- "$ref": "#/definitions/ssl_forced"
- },
- "hsts_enabled": {
- "$ref": "#/definitions/hsts_enabled"
- },
- "hsts_subdomains": {
- "$ref": "#/definitions/hsts_enabled"
- },
- "http2_support": {
- "$ref": "#/definitions/http2_support"
- },
- "block_exploits": {
- "$ref": "#/definitions/block_exploits"
- },
- "caching_enabled": {
- "$ref": "#/definitions/caching_enabled"
- },
- "allow_websocket_upgrade": {
- "$ref": "#/definitions/allow_websocket_upgrade"
- },
- "access_list_id": {
- "$ref": "#/definitions/access_list_id"
- },
- "advanced_config": {
- "$ref": "#/definitions/advanced_config"
- },
- "enabled": {
- "$ref": "#/definitions/enabled"
- },
- "meta": {
- "$ref": "#/definitions/meta"
- },
- "locations": {
- "$ref": "#/definitions/locations"
- }
- }
- },
- "targetSchema": {
- "properties": {
- "$ref": "#/properties"
- }
- }
- },
- {
- "title": "Update",
- "description": "Updates a existing Proxy Host",
- "href": "/nginx/proxy-hosts/{definitions.identity.example}",
- "access": "private",
- "method": "PUT",
- "rel": "update",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "schema": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "domain_names": {
- "$ref": "#/definitions/domain_names"
- },
- "forward_scheme": {
- "$ref": "#/definitions/forward_scheme"
- },
- "forward_host": {
- "$ref": "#/definitions/forward_host"
- },
- "forward_port": {
- "$ref": "#/definitions/forward_port"
- },
- "certificate_id": {
- "$ref": "#/definitions/certificate_id"
- },
- "ssl_forced": {
- "$ref": "#/definitions/ssl_forced"
- },
- "hsts_enabled": {
- "$ref": "#/definitions/hsts_enabled"
- },
- "hsts_subdomains": {
- "$ref": "#/definitions/hsts_enabled"
- },
- "http2_support": {
- "$ref": "#/definitions/http2_support"
- },
- "block_exploits": {
- "$ref": "#/definitions/block_exploits"
- },
- "caching_enabled": {
- "$ref": "#/definitions/caching_enabled"
- },
- "allow_websocket_upgrade": {
- "$ref": "#/definitions/allow_websocket_upgrade"
- },
- "access_list_id": {
- "$ref": "#/definitions/access_list_id"
- },
- "advanced_config": {
- "$ref": "#/definitions/advanced_config"
- },
- "enabled": {
- "$ref": "#/definitions/enabled"
- },
- "meta": {
- "$ref": "#/definitions/meta"
- },
- "locations": {
- "$ref": "#/definitions/locations"
- }
- }
- },
- "targetSchema": {
- "properties": {
- "$ref": "#/properties"
- }
- }
- },
- {
- "title": "Delete",
- "description": "Deletes a existing Proxy Host",
- "href": "/nginx/proxy-hosts/{definitions.identity.example}",
- "access": "private",
- "method": "DELETE",
- "rel": "delete",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "targetSchema": {
- "type": "boolean"
- }
- },
- {
- "title": "Enable",
- "description": "Enables a existing Proxy Host",
- "href": "/nginx/proxy-hosts/{definitions.identity.example}/enable",
- "access": "private",
- "method": "POST",
- "rel": "update",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "targetSchema": {
- "type": "boolean"
- }
- },
- {
- "title": "Disable",
- "description": "Disables a existing Proxy Host",
- "href": "/nginx/proxy-hosts/{definitions.identity.example}/disable",
- "access": "private",
- "method": "POST",
- "rel": "update",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "targetSchema": {
- "type": "boolean"
- }
- }
- ]
-}
diff --git a/backend/schema/endpoints/redirection-hosts.json b/backend/schema/endpoints/redirection-hosts.json
deleted file mode 100644
index 14a469985..000000000
--- a/backend/schema/endpoints/redirection-hosts.json
+++ /dev/null
@@ -1,305 +0,0 @@
-{
- "$schema": "http://json-schema.org/draft-07/schema#",
- "$id": "endpoints/redirection-hosts",
- "title": "Redirection Hosts",
- "description": "Endpoints relating to Redirection Hosts",
- "stability": "stable",
- "type": "object",
- "definitions": {
- "id": {
- "$ref": "../definitions.json#/definitions/id"
- },
- "created_on": {
- "$ref": "../definitions.json#/definitions/created_on"
- },
- "modified_on": {
- "$ref": "../definitions.json#/definitions/modified_on"
- },
- "domain_names": {
- "$ref": "../definitions.json#/definitions/domain_names"
- },
- "forward_http_code": {
- "$ref": "../definitions.json#/definitions/http_code"
- },
- "forward_scheme": {
- "$ref": "../definitions.json#/definitions/scheme"
- },
- "forward_domain_name": {
- "$ref": "../definitions.json#/definitions/domain_name"
- },
- "preserve_path": {
- "description": "Should the path be preserved",
- "example": true,
- "type": "boolean"
- },
- "certificate_id": {
- "$ref": "../definitions.json#/definitions/certificate_id"
- },
- "ssl_forced": {
- "$ref": "../definitions.json#/definitions/ssl_forced"
- },
- "hsts_enabled": {
- "$ref": "../definitions.json#/definitions/hsts_enabled"
- },
- "hsts_subdomains": {
- "$ref": "../definitions.json#/definitions/hsts_subdomains"
- },
- "http2_support": {
- "$ref": "../definitions.json#/definitions/http2_support"
- },
- "block_exploits": {
- "$ref": "../definitions.json#/definitions/block_exploits"
- },
- "advanced_config": {
- "type": "string"
- },
- "enabled": {
- "$ref": "../definitions.json#/definitions/enabled"
- },
- "meta": {
- "type": "object"
- }
- },
- "properties": {
- "id": {
- "$ref": "#/definitions/id"
- },
- "created_on": {
- "$ref": "#/definitions/created_on"
- },
- "modified_on": {
- "$ref": "#/definitions/modified_on"
- },
- "domain_names": {
- "$ref": "#/definitions/domain_names"
- },
- "forward_http_code": {
- "$ref": "#/definitions/forward_http_code"
- },
- "forward_scheme": {
- "$ref": "#/definitions/forward_scheme"
- },
- "forward_domain_name": {
- "$ref": "#/definitions/forward_domain_name"
- },
- "preserve_path": {
- "$ref": "#/definitions/preserve_path"
- },
- "certificate_id": {
- "$ref": "#/definitions/certificate_id"
- },
- "ssl_forced": {
- "$ref": "#/definitions/ssl_forced"
- },
- "hsts_enabled": {
- "$ref": "#/definitions/hsts_enabled"
- },
- "hsts_subdomains": {
- "$ref": "#/definitions/hsts_subdomains"
- },
- "http2_support": {
- "$ref": "#/definitions/http2_support"
- },
- "block_exploits": {
- "$ref": "#/definitions/block_exploits"
- },
- "advanced_config": {
- "$ref": "#/definitions/advanced_config"
- },
- "enabled": {
- "$ref": "#/definitions/enabled"
- },
- "meta": {
- "$ref": "#/definitions/meta"
- }
- },
- "links": [
- {
- "title": "List",
- "description": "Returns a list of Redirection Hosts",
- "href": "/nginx/redirection-hosts",
- "access": "private",
- "method": "GET",
- "rel": "self",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "targetSchema": {
- "type": "array",
- "items": {
- "$ref": "#/properties"
- }
- }
- },
- {
- "title": "Create",
- "description": "Creates a new Redirection Host",
- "href": "/nginx/redirection-hosts",
- "access": "private",
- "method": "POST",
- "rel": "create",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "schema": {
- "type": "object",
- "additionalProperties": false,
- "required": [
- "domain_names",
- "forward_scheme",
- "forward_http_code",
- "forward_domain_name"
- ],
- "properties": {
- "domain_names": {
- "$ref": "#/definitions/domain_names"
- },
- "forward_http_code": {
- "$ref": "#/definitions/forward_http_code"
- },
- "forward_scheme": {
- "$ref": "#/definitions/forward_scheme"
- },
- "forward_domain_name": {
- "$ref": "#/definitions/forward_domain_name"
- },
- "preserve_path": {
- "$ref": "#/definitions/preserve_path"
- },
- "certificate_id": {
- "$ref": "#/definitions/certificate_id"
- },
- "ssl_forced": {
- "$ref": "#/definitions/ssl_forced"
- },
- "hsts_enabled": {
- "$ref": "#/definitions/hsts_enabled"
- },
- "hsts_subdomains": {
- "$ref": "#/definitions/hsts_enabled"
- },
- "http2_support": {
- "$ref": "#/definitions/http2_support"
- },
- "block_exploits": {
- "$ref": "#/definitions/block_exploits"
- },
- "advanced_config": {
- "$ref": "#/definitions/advanced_config"
- },
- "meta": {
- "$ref": "#/definitions/meta"
- }
- }
- },
- "targetSchema": {
- "properties": {
- "$ref": "#/properties"
- }
- }
- },
- {
- "title": "Update",
- "description": "Updates a existing Redirection Host",
- "href": "/nginx/redirection-hosts/{definitions.identity.example}",
- "access": "private",
- "method": "PUT",
- "rel": "update",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "schema": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "domain_names": {
- "$ref": "#/definitions/domain_names"
- },
- "forward_http_code": {
- "$ref": "#/definitions/forward_http_code"
- },
- "forward_scheme": {
- "$ref": "#/definitions/forward_scheme"
- },
- "forward_domain_name": {
- "$ref": "#/definitions/forward_domain_name"
- },
- "preserve_path": {
- "$ref": "#/definitions/preserve_path"
- },
- "certificate_id": {
- "$ref": "#/definitions/certificate_id"
- },
- "ssl_forced": {
- "$ref": "#/definitions/ssl_forced"
- },
- "hsts_enabled": {
- "$ref": "#/definitions/hsts_enabled"
- },
- "hsts_subdomains": {
- "$ref": "#/definitions/hsts_enabled"
- },
- "http2_support": {
- "$ref": "#/definitions/http2_support"
- },
- "block_exploits": {
- "$ref": "#/definitions/block_exploits"
- },
- "advanced_config": {
- "$ref": "#/definitions/advanced_config"
- },
- "meta": {
- "$ref": "#/definitions/meta"
- }
- }
- },
- "targetSchema": {
- "properties": {
- "$ref": "#/properties"
- }
- }
- },
- {
- "title": "Delete",
- "description": "Deletes a existing Redirection Host",
- "href": "/nginx/redirection-hosts/{definitions.identity.example}",
- "access": "private",
- "method": "DELETE",
- "rel": "delete",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "targetSchema": {
- "type": "boolean"
- }
- },
- {
- "title": "Enable",
- "description": "Enables a existing Redirection Host",
- "href": "/nginx/redirection-hosts/{definitions.identity.example}/enable",
- "access": "private",
- "method": "POST",
- "rel": "update",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "targetSchema": {
- "type": "boolean"
- }
- },
- {
- "title": "Disable",
- "description": "Disables a existing Redirection Host",
- "href": "/nginx/redirection-hosts/{definitions.identity.example}/disable",
- "access": "private",
- "method": "POST",
- "rel": "update",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "targetSchema": {
- "type": "boolean"
- }
- }
- ]
-}
diff --git a/backend/schema/endpoints/settings.json b/backend/schema/endpoints/settings.json
deleted file mode 100644
index 29e2865ae..000000000
--- a/backend/schema/endpoints/settings.json
+++ /dev/null
@@ -1,99 +0,0 @@
-{
- "$schema": "http://json-schema.org/draft-07/schema#",
- "$id": "endpoints/settings",
- "title": "Settings",
- "description": "Endpoints relating to Settings",
- "stability": "stable",
- "type": "object",
- "definitions": {
- "id": {
- "$ref": "../definitions.json#/definitions/setting_id"
- },
- "name": {
- "description": "Name",
- "example": "Default Site",
- "type": "string",
- "minLength": 2,
- "maxLength": 100
- },
- "description": {
- "description": "Description",
- "example": "Default Site",
- "type": "string",
- "minLength": 2,
- "maxLength": 255
- },
- "value": {
- "description": "Value",
- "example": "404",
- "type": "string",
- "maxLength": 255
- },
- "meta": {
- "type": "object"
- }
- },
- "links": [
- {
- "title": "List",
- "description": "Returns a list of Settings",
- "href": "/settings",
- "access": "private",
- "method": "GET",
- "rel": "self",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "targetSchema": {
- "type": "array",
- "items": {
- "$ref": "#/properties"
- }
- }
- },
- {
- "title": "Update",
- "description": "Updates a existing Setting",
- "href": "/settings/{definitions.identity.example}",
- "access": "private",
- "method": "PUT",
- "rel": "update",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "schema": {
- "type": "object",
- "properties": {
- "value": {
- "$ref": "#/definitions/value"
- },
- "meta": {
- "$ref": "#/definitions/meta"
- }
- }
- },
- "targetSchema": {
- "properties": {
- "$ref": "#/properties"
- }
- }
- }
- ],
- "properties": {
- "id": {
- "$ref": "#/definitions/id"
- },
- "name": {
- "$ref": "#/definitions/description"
- },
- "description": {
- "$ref": "#/definitions/description"
- },
- "value": {
- "$ref": "#/definitions/value"
- },
- "meta": {
- "$ref": "#/definitions/meta"
- }
- }
-}
diff --git a/backend/schema/endpoints/streams.json b/backend/schema/endpoints/streams.json
deleted file mode 100644
index cc4dd6f58..000000000
--- a/backend/schema/endpoints/streams.json
+++ /dev/null
@@ -1,234 +0,0 @@
-{
- "$schema": "http://json-schema.org/draft-07/schema#",
- "$id": "endpoints/streams",
- "title": "Streams",
- "description": "Endpoints relating to Streams",
- "stability": "stable",
- "type": "object",
- "definitions": {
- "id": {
- "$ref": "../definitions.json#/definitions/id"
- },
- "created_on": {
- "$ref": "../definitions.json#/definitions/created_on"
- },
- "modified_on": {
- "$ref": "../definitions.json#/definitions/modified_on"
- },
- "incoming_port": {
- "type": "string",
- "pattern": "^([0-9]+|[0-9]+-[0-9]+)$",
- "maxLength": 11
- },
- "forwarding_host": {
- "anyOf": [
- {
- "$ref": "../definitions.json#/definitions/domain_name"
- },
- {
- "type": "string",
- "format": "ipv4"
- },
- {
- "type": "string",
- "format": "ipv6"
- }
- ]
- },
- "forwarding_port": {
- "type": "integer",
- "minimum": 1,
- "maximum": 65535
- },
- "tcp_forwarding": {
- "type": "boolean"
- },
- "udp_forwarding": {
- "type": "boolean"
- },
- "enabled": {
- "$ref": "../definitions.json#/definitions/enabled"
- },
- "meta": {
- "type": "object"
- }
- },
- "properties": {
- "id": {
- "$ref": "#/definitions/id"
- },
- "created_on": {
- "$ref": "#/definitions/created_on"
- },
- "modified_on": {
- "$ref": "#/definitions/modified_on"
- },
- "incoming_port": {
- "$ref": "#/definitions/incoming_port"
- },
- "forwarding_host": {
- "$ref": "#/definitions/forwarding_host"
- },
- "forwarding_port": {
- "$ref": "#/definitions/forwarding_port"
- },
- "tcp_forwarding": {
- "$ref": "#/definitions/tcp_forwarding"
- },
- "udp_forwarding": {
- "$ref": "#/definitions/udp_forwarding"
- },
- "enabled": {
- "$ref": "#/definitions/enabled"
- },
- "meta": {
- "$ref": "#/definitions/meta"
- }
- },
- "links": [
- {
- "title": "List",
- "description": "Returns a list of Streams",
- "href": "/nginx/streams",
- "access": "private",
- "method": "GET",
- "rel": "self",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "targetSchema": {
- "type": "array",
- "items": {
- "$ref": "#/properties"
- }
- }
- },
- {
- "title": "Create",
- "description": "Creates a new Stream",
- "href": "/nginx/streams",
- "access": "private",
- "method": "POST",
- "rel": "create",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "schema": {
- "type": "object",
- "additionalProperties": false,
- "required": [
- "incoming_port",
- "forwarding_host",
- "forwarding_port"
- ],
- "properties": {
- "incoming_port": {
- "$ref": "#/definitions/incoming_port"
- },
- "forwarding_host": {
- "$ref": "#/definitions/forwarding_host"
- },
- "forwarding_port": {
- "$ref": "#/definitions/forwarding_port"
- },
- "tcp_forwarding": {
- "$ref": "#/definitions/tcp_forwarding"
- },
- "udp_forwarding": {
- "$ref": "#/definitions/udp_forwarding"
- },
- "meta": {
- "$ref": "#/definitions/meta"
- }
- }
- },
- "targetSchema": {
- "properties": {
- "$ref": "#/properties"
- }
- }
- },
- {
- "title": "Update",
- "description": "Updates a existing Stream",
- "href": "/nginx/streams/{definitions.identity.example}",
- "access": "private",
- "method": "PUT",
- "rel": "update",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "schema": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "incoming_port": {
- "$ref": "#/definitions/incoming_port"
- },
- "forwarding_host": {
- "$ref": "#/definitions/forwarding_host"
- },
- "forwarding_port": {
- "$ref": "#/definitions/forwarding_port"
- },
- "tcp_forwarding": {
- "$ref": "#/definitions/tcp_forwarding"
- },
- "udp_forwarding": {
- "$ref": "#/definitions/udp_forwarding"
- },
- "meta": {
- "$ref": "#/definitions/meta"
- }
- }
- },
- "targetSchema": {
- "properties": {
- "$ref": "#/properties"
- }
- }
- },
- {
- "title": "Delete",
- "description": "Deletes a existing Stream",
- "href": "/nginx/streams/{definitions.identity.example}",
- "access": "private",
- "method": "DELETE",
- "rel": "delete",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "targetSchema": {
- "type": "boolean"
- }
- },
- {
- "title": "Enable",
- "description": "Enables a existing Stream",
- "href": "/nginx/streams/{definitions.identity.example}/enable",
- "access": "private",
- "method": "POST",
- "rel": "update",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "targetSchema": {
- "type": "boolean"
- }
- },
- {
- "title": "Disable",
- "description": "Disables a existing Stream",
- "href": "/nginx/streams/{definitions.identity.example}/disable",
- "access": "private",
- "method": "POST",
- "rel": "update",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "targetSchema": {
- "type": "boolean"
- }
- }
- ]
-}
diff --git a/backend/schema/endpoints/tokens.json b/backend/schema/endpoints/tokens.json
deleted file mode 100644
index 920af63f4..000000000
--- a/backend/schema/endpoints/tokens.json
+++ /dev/null
@@ -1,100 +0,0 @@
-{
- "$schema": "http://json-schema.org/draft-07/schema#",
- "$id": "endpoints/tokens",
- "title": "Token",
- "description": "Tokens are required to authenticate against the API",
- "stability": "stable",
- "type": "object",
- "definitions": {
- "identity": {
- "description": "Email Address or other 3rd party providers identifier",
- "example": "john@example.com",
- "type": "string"
- },
- "secret": {
- "description": "A password or key",
- "example": "correct horse battery staple",
- "type": "string"
- },
- "token": {
- "description": "JWT",
- "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.O_frfYM8RzmRsUNigHtu0_jZ_utSejyr1axMGa8rlsk",
- "type": "string"
- },
- "expires": {
- "description": "Token expiry time",
- "format": "date-time",
- "type": "string"
- },
- "scope": {
- "description": "Scope of the Token, defaults to 'user'",
- "example": "user",
- "type": "string"
- }
- },
- "links": [
- {
- "title": "Create",
- "description": "Creates a new token.",
- "href": "/tokens",
- "access": "public",
- "method": "POST",
- "rel": "create",
- "schema": {
- "type": "object",
- "required": [
- "identity",
- "secret"
- ],
- "properties": {
- "identity": {
- "$ref": "#/definitions/identity"
- },
- "secret": {
- "$ref": "#/definitions/secret"
- },
- "scope": {
- "$ref": "#/definitions/scope"
- }
- }
- },
- "targetSchema": {
- "type": "object",
- "properties": {
- "token": {
- "$ref": "#/definitions/token"
- },
- "expires": {
- "$ref": "#/definitions/expires"
- }
- }
- }
- },
- {
- "title": "Refresh",
- "description": "Returns a new token.",
- "href": "/tokens",
- "access": "private",
- "method": "GET",
- "rel": "self",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "schema": {},
- "targetSchema": {
- "type": "object",
- "properties": {
- "token": {
- "$ref": "#/definitions/token"
- },
- "expires": {
- "$ref": "#/definitions/expires"
- },
- "scope": {
- "$ref": "#/definitions/scope"
- }
- }
- }
- }
- ]
-}
diff --git a/backend/schema/endpoints/users.json b/backend/schema/endpoints/users.json
deleted file mode 100644
index 5adff9025..000000000
--- a/backend/schema/endpoints/users.json
+++ /dev/null
@@ -1,287 +0,0 @@
-{
- "$schema": "http://json-schema.org/draft-07/schema#",
- "$id": "endpoints/users",
- "title": "Users",
- "description": "Endpoints relating to Users",
- "stability": "stable",
- "type": "object",
- "definitions": {
- "id": {
- "$ref": "../definitions.json#/definitions/id"
- },
- "created_on": {
- "$ref": "../definitions.json#/definitions/created_on"
- },
- "modified_on": {
- "$ref": "../definitions.json#/definitions/modified_on"
- },
- "name": {
- "description": "Name",
- "example": "Jamie Curnow",
- "type": "string",
- "minLength": 2,
- "maxLength": 100
- },
- "nickname": {
- "description": "Nickname",
- "example": "Jamie",
- "type": "string",
- "minLength": 2,
- "maxLength": 50
- },
- "email": {
- "$ref": "../definitions.json#/definitions/email"
- },
- "avatar": {
- "description": "Avatar",
- "example": "http://somewhere.jpg",
- "type": "string",
- "minLength": 2,
- "maxLength": 150,
- "readOnly": true
- },
- "roles": {
- "description": "Roles",
- "example": [
- "admin"
- ],
- "type": "array"
- },
- "is_disabled": {
- "description": "Is Disabled",
- "example": false,
- "type": "boolean"
- }
- },
- "links": [
- {
- "title": "List",
- "description": "Returns a list of Users",
- "href": "/users",
- "access": "private",
- "method": "GET",
- "rel": "self",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "targetSchema": {
- "type": "array",
- "items": {
- "$ref": "#/properties"
- }
- }
- },
- {
- "title": "Create",
- "description": "Creates a new User",
- "href": "/users",
- "access": "private",
- "method": "POST",
- "rel": "create",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "schema": {
- "type": "object",
- "required": [
- "name",
- "nickname",
- "email"
- ],
- "properties": {
- "name": {
- "$ref": "#/definitions/name"
- },
- "nickname": {
- "$ref": "#/definitions/nickname"
- },
- "email": {
- "$ref": "#/definitions/email"
- },
- "roles": {
- "$ref": "#/definitions/roles"
- },
- "is_disabled": {
- "$ref": "#/definitions/is_disabled"
- },
- "auth": {
- "type": "object",
- "description": "Auth Credentials",
- "example": {
- "type": "password",
- "secret": "bigredhorsebanana"
- }
- }
- }
- },
- "targetSchema": {
- "properties": {
- "$ref": "#/properties"
- }
- }
- },
- {
- "title": "Update",
- "description": "Updates a existing User",
- "href": "/users/{definitions.identity.example}",
- "access": "private",
- "method": "PUT",
- "rel": "update",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "schema": {
- "type": "object",
- "properties": {
- "name": {
- "$ref": "#/definitions/name"
- },
- "nickname": {
- "$ref": "#/definitions/nickname"
- },
- "email": {
- "$ref": "#/definitions/email"
- },
- "roles": {
- "$ref": "#/definitions/roles"
- },
- "is_disabled": {
- "$ref": "#/definitions/is_disabled"
- }
- }
- },
- "targetSchema": {
- "properties": {
- "$ref": "#/properties"
- }
- }
- },
- {
- "title": "Delete",
- "description": "Deletes a existing User",
- "href": "/users/{definitions.identity.example}",
- "access": "private",
- "method": "DELETE",
- "rel": "delete",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "targetSchema": {
- "type": "boolean"
- }
- },
- {
- "title": "Set Password",
- "description": "Sets a password for an existing User",
- "href": "/users/{definitions.identity.example}/auth",
- "access": "private",
- "method": "PUT",
- "rel": "update",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "schema": {
- "type": "object",
- "required": [
- "type",
- "secret"
- ],
- "properties": {
- "type": {
- "type": "string",
- "pattern": "^password$"
- },
- "current": {
- "type": "string",
- "minLength": 1,
- "maxLength": 99
- },
- "secret": {
- "type": "string",
- "minLength": 8,
- "maxLength": 99
- }
- }
- },
- "targetSchema": {
- "type": "boolean"
- }
- },
- {
- "title": "Set Permissions",
- "description": "Sets Permissions for a User",
- "href": "/users/{definitions.identity.example}/permissions",
- "access": "private",
- "method": "PUT",
- "rel": "update",
- "http_header": {
- "$ref": "../examples.json#/definitions/auth_header"
- },
- "schema": {
- "type": "object",
- "properties": {
- "visibility": {
- "type": "string",
- "pattern": "^(all|user)$"
- },
- "access_lists": {
- "type": "string",
- "pattern": "^(hidden|view|manage)$"
- },
- "dead_hosts": {
- "type": "string",
- "pattern": "^(hidden|view|manage)$"
- },
- "proxy_hosts": {
- "type": "string",
- "pattern": "^(hidden|view|manage)$"
- },
- "redirection_hosts": {
- "type": "string",
- "pattern": "^(hidden|view|manage)$"
- },
- "streams": {
- "type": "string",
- "pattern": "^(hidden|view|manage)$"
- },
- "certificates": {
- "type": "string",
- "pattern": "^(hidden|view|manage)$"
- }
- }
- },
- "targetSchema": {
- "type": "boolean"
- }
- }
- ],
- "properties": {
- "id": {
- "$ref": "#/definitions/id"
- },
- "created_on": {
- "$ref": "#/definitions/created_on"
- },
- "modified_on": {
- "$ref": "#/definitions/modified_on"
- },
- "name": {
- "$ref": "#/definitions/name"
- },
- "nickname": {
- "$ref": "#/definitions/nickname"
- },
- "email": {
- "$ref": "#/definitions/email"
- },
- "avatar": {
- "$ref": "#/definitions/avatar"
- },
- "roles": {
- "$ref": "#/definitions/roles"
- },
- "is_disabled": {
- "$ref": "#/definitions/is_disabled"
- }
- }
-}
diff --git a/backend/schema/examples.json b/backend/schema/examples.json
deleted file mode 100644
index 37bc6c4d3..000000000
--- a/backend/schema/examples.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "$schema": "http://json-schema.org/draft-07/schema#",
- "$id": "examples",
- "type": "object",
- "definitions": {
- "name": {
- "description": "Name",
- "example": "John Smith",
- "type": "string",
- "minLength": 1,
- "maxLength": 255
- },
- "auth_header": {
- "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.O_frfYM8RzmRsUNigHtu0_jZ_utSejyr1axMGa8rlsk",
- "X-API-Version": "next"
- },
- "token": {
- "type": "string",
- "description": "JWT",
- "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.O_frfYM8RzmRsUNigHtu0_jZ_utSejyr1axMGa8rlsk"
- }
- }
-}
diff --git a/backend/schema/index.json b/backend/schema/index.json
deleted file mode 100644
index 28bea1b28..000000000
--- a/backend/schema/index.json
+++ /dev/null
@@ -1,42 +0,0 @@
-{
- "$schema": "http://json-schema.org/draft-07/schema#",
- "$id": "root",
- "title": "NPMplus REST API",
- "description": "This is the NPMplus REST API",
- "version": "2.0.0",
- "links": [
- {
- "href": "http://npm.example.com/api",
- "rel": "self"
- }
- ],
- "properties": {
- "tokens": {
- "$ref": "endpoints/tokens.json"
- },
- "users": {
- "$ref": "endpoints/users.json"
- },
- "proxy-hosts": {
- "$ref": "endpoints/proxy-hosts.json"
- },
- "redirection-hosts": {
- "$ref": "endpoints/redirection-hosts.json"
- },
- "dead-hosts": {
- "$ref": "endpoints/dead-hosts.json"
- },
- "streams": {
- "$ref": "endpoints/streams.json"
- },
- "certificates": {
- "$ref": "endpoints/certificates.json"
- },
- "access-lists": {
- "$ref": "endpoints/access-lists.json"
- },
- "settings": {
- "$ref": "endpoints/settings.json"
- }
- }
-}
diff --git a/backend/setup.js b/backend/setup.js
deleted file mode 100644
index c5b89bb64..000000000
--- a/backend/setup.js
+++ /dev/null
@@ -1,148 +0,0 @@
-const config = require('./lib/config');
-const logger = require('./logger').setup;
-const certificateModel = require('./models/certificate');
-const userModel = require('./models/user');
-const userPermissionModel = require('./models/user_permission');
-const utils = require('./lib/utils');
-const authModel = require('./models/auth');
-const settingModel = require('./models/setting');
-const certbot = require('./lib/certbot');
-
-/**
- * Creates a default admin users if one doesn't already exist in the database
- *
- * @returns {Promise}
- */
-const setupDefaultUser = () => {
- return userModel
- .query()
- .select(userModel.raw('COUNT(`id`) as `count`'))
- .where('is_deleted', 0)
- .first()
- .then((row) => {
- if (!row.count) {
- // Create a new user and set password
- let email = process.env.INITIAL_ADMIN_EMAIL || 'admin@example.org';
- let password = process.env.INITIAL_ADMIN_PASSWORD || 'iArhP1j7p1P6TA92FA2FMbbUGYqwcYzxC4AVEe12Wbi94FY9gNN62aKyF1shrvG4NycjjX9KfmDQiwkLZH1ZDR9xMjiG2QmoHXi';
-
- logger.info('Creating a new user: ' + email + ' with password: ' + password);
-
- const data = {
- is_deleted: 0,
- email: email,
- name: 'Administrator',
- nickname: 'Admin',
- avatar: '',
- roles: ['admin'],
- };
-
- return userModel
- .query()
- .insertAndFetch(data)
- .then((user) => {
- return authModel
- .query()
- .insert({
- user_id: user.id,
- type: 'password',
- secret: password,
- meta: {},
- })
- .then(() => {
- return userPermissionModel.query().insert({
- user_id: user.id,
- visibility: 'all',
- proxy_hosts: 'manage',
- redirection_hosts: 'manage',
- dead_hosts: 'manage',
- streams: 'manage',
- access_lists: 'manage',
- certificates: 'manage',
- });
- });
- })
- .then(() => {
- logger.info('Initial admin setup completed');
- });
- } else if (config.debug()) {
- logger.info('Admin user setup not required');
- }
- });
-};
-
-/**
- * Creates default settings if they don't already exist in the database
- *
- * @returns {Promise}
- */
-const setupDefaultSettings = () => {
- return settingModel
- .query()
- .select(settingModel.raw('COUNT(`id`) as `count`'))
- .where({ id: 'default-site' })
- .first()
- .then((row) => {
- if (!row.count) {
- settingModel
- .query()
- .insert({
- id: 'default-site',
- name: 'Default Site',
- description: 'What to show when Nginx is hit with an unknown Host',
- value: 'congratulations',
- meta: {},
- })
- .then(() => {
- logger.info('Default settings added');
- });
- }
- if (config.debug()) {
- logger.info('Default setting setup not required');
- }
- });
-};
-
-/**
- * Installs all Certbot plugins which are required for an installed certificate
- *
- * @returns {Promise}
- */
-const setupCertbotPlugins = () => {
- return certificateModel
- .query()
- .where('is_deleted', 0)
- .andWhere('provider', 'letsencrypt')
- .then((certificates) => {
- if (certificates && certificates.length) {
- const plugins = [];
- const promises = [];
-
- certificates.map(function (certificate) {
- if (certificate.meta && certificate.meta.dns_challenge === true) {
- if (plugins.indexOf(certificate.meta.dns_provider) === -1) {
- plugins.push(certificate.meta.dns_provider);
- }
-
- // Make sure credentials file exists
- const credentials_loc = '/data/tls/certbot/credentials/credentials-' + certificate.id;
- // Escape single quotes and backslashes
- const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll("'", "\\'").replaceAll('\\', '\\\\');
- const credentials_cmd = "[ -f '" + credentials_loc + "' ] || { mkdir -p /data/tls/certbot/credentials 2> /dev/null; echo '" + escapedCredentials + "' > '" + credentials_loc + "' && chmod 600 '" + credentials_loc + "'; }";
- promises.push(utils.exec(credentials_cmd));
- }
- });
-
- return certbot.installPlugins(plugins).then(() => {
- if (promises.length) {
- return Promise.all(promises).then(() => {
- logger.info('Added Certbot plugins ' + plugins.join(', '));
- });
- }
- });
- }
- });
-};
-
-module.exports = function () {
- return setupDefaultUser().then(setupDefaultSettings).then(setupCertbotPlugins);
-};
diff --git a/backend/sqlite-vaccum.js b/backend/sqlite-vaccum.js
deleted file mode 100755
index b922640c4..000000000
--- a/backend/sqlite-vaccum.js
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/usr/bin/env node
-
-const Database = require('better-sqlite3');
-const db = new Database(process.env.DB_SQLITE_FILE);
-
-db.pragma('journal_mode = WAL');
-db.pragma('auto_vacuum = 1');
-db.exec('VACUUM;');
-db.close();
diff --git a/backend/templates/_access.conf b/backend/templates/_access.conf
deleted file mode 100644
index 1562e6edb..000000000
--- a/backend/templates/_access.conf
+++ /dev/null
@@ -1,25 +0,0 @@
-{% if access_list_id > 0 %}
- {% if access_list.items.length > 0 %}
- # Authorization
- auth_basic "Authorization required";
- auth_basic_user_file /data/etc/access/{{ access_list_id }};
-
- {% if access_list.pass_auth == 0 %}
- proxy_set_header Authorization "";
- {% endif %}
-
- {% endif %}
-
- # Access Rules: {{ access_list.clients | size }} total
- {% for client in access_list.clients %}
- {{client | nginxAccessRule}}
- {% endfor %}
- deny all;
-
- # Access checks must...
- {% if access_list.satisfy_any == 1 %}
- satisfy any;
- {% else %}
- satisfy all;
- {% endif %}
-{% endif %}
diff --git a/backend/templates/_brotli.conf b/backend/templates/_brotli.conf
deleted file mode 100644
index 39ef12863..000000000
--- a/backend/templates/_brotli.conf
+++ /dev/null
@@ -1,4 +0,0 @@
-{% if http2_support == 1 or http2_support == true -%}
- # Enable Brotli
- include conf.d/include/brotli.conf;
-{% endif %}
diff --git a/backend/templates/_certificates.conf b/backend/templates/_certificates.conf
deleted file mode 100644
index aff3bde32..000000000
--- a/backend/templates/_certificates.conf
+++ /dev/null
@@ -1,14 +0,0 @@
-{% if certificate and certificate_id > 0 -%}
-{% if certificate.provider == "letsencrypt" %}
- # Certbot TLS
- include conf.d/include/tls-ciphers.conf;
- ssl_certificate /data/tls/certbot/live/npm-{{ certificate_id }}/fullchain.pem;
- ssl_certificate_key /data/tls/certbot/live/npm-{{ certificate_id }}/privkey.pem;
- ssl_stapling_file /data/tls/certbot/live/npm-{{ certificate_id }}.der;
-{% else %}
- # Custom TLS
- include conf.d/include/tls-ciphers.conf;
- ssl_certificate /data/tls/custom/npm-{{ certificate_id }}/fullchain.pem;
- ssl_certificate_key /data/tls/custom/npm-{{ certificate_id }}/privkey.pem;
-{% endif %}
-{% endif %}
diff --git a/backend/templates/_forced_tls.conf b/backend/templates/_forced_tls.conf
deleted file mode 100644
index 3b0226a2a..000000000
--- a/backend/templates/_forced_tls.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-{% if certificate and certificate_id > 0 -%}
-{% if ssl_forced == 1 or ssl_forced == true %}
- # Force TLS
- include conf.d/include/force-tls.conf;
-{% endif %}
-{% endif %}
diff --git a/backend/templates/_header_comment.conf b/backend/templates/_header_comment.conf
deleted file mode 100644
index 8f996d34f..000000000
--- a/backend/templates/_header_comment.conf
+++ /dev/null
@@ -1,3 +0,0 @@
-# ------------------------------------------------------------
-# {{ domain_names | join: ", " }}
-# ------------------------------------------------------------
\ No newline at end of file
diff --git a/backend/templates/_hsts.conf b/backend/templates/_hsts.conf
deleted file mode 100644
index c0a743ed2..000000000
--- a/backend/templates/_hsts.conf
+++ /dev/null
@@ -1,17 +0,0 @@
-{% if certificate and certificate_id > 0 -%}
-{% if ssl_forced == 1 or ssl_forced == true %}
-{% if hsts_enabled == 1 or hsts_enabled == true %}
- more_clear_headers "Expect-CT";
- include conf.d/include/hsts.conf;
-{% endif %}
-{% endif %}
-{% endif %}
-
-{% unless certificate and certificate_id > 0 -%}
-{% unless ssl_forced == 1 or ssl_forced == true %}
-{% unless hsts_enabled == 1 or hsts_enabled == true %}
- more_clear_headers "Expect-CT";
- more_clear_headers "Strict-Transport-Security";
-{% endunless %}
-{% endunless %}
-{% endunless %}
diff --git a/backend/templates/_listen.conf b/backend/templates/_listen.conf
deleted file mode 100644
index 8ad3be224..000000000
--- a/backend/templates/_listen.conf
+++ /dev/null
@@ -1,20 +0,0 @@
- listen unix:/run/nginx-{{ id }}.sock;
-
- listen 80;
- listen [::]:80;
-
-{% if certificate and certificate_id > 0 %}
- listen 443 ssl;
- listen [::]:443 ssl;
-
- listen 443 quic;
- listen [::]:443 quic;
-
-{% if hsts_subdomains == 1 or hsts_subdomains == true %}
- more_set_headers 'Alt-Svc: h3=":443"; ma=86400';
-{% else %}
- more_clear_headers "Alt-Svc";
- http3 off;
-{% endif %}
-{% endif %}
- server_name {{ domain_names | join: " " }};
diff --git a/backend/templates/_location.conf b/backend/templates/_location.conf
deleted file mode 100644
index 1ad86b3ac..000000000
--- a/backend/templates/_location.conf
+++ /dev/null
@@ -1,17 +0,0 @@
-location {{ path }} {
- {{ advanced_config }}
-
- set $forward_path "{{ forward_path }}";
-
- {% if allow_websocket_upgrade == 1 or allow_websocket_upgrade == true %}
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection $connection_upgrade;
- {% endif %}
-
- include conf.d/include/proxy-location.conf;
- proxy_set_header X-Forwarded-Host $host{{ path }};
- if ($forward_path = "") {
- rewrite ^{{ path }}(/.*)$ $1 break;
- }
- proxy_pass {{ forward_scheme }}://{{ forward_host }}:{{ forward_port }}{{ forward_path }};
-}
diff --git a/backend/templates/dead_host.conf b/backend/templates/dead_host.conf
deleted file mode 100644
index d17b45928..000000000
--- a/backend/templates/dead_host.conf
+++ /dev/null
@@ -1,27 +0,0 @@
-{% include "_header_comment.conf" %}
-
-{% if enabled == 1 or enabled == true %}
-server {
-{% include "_listen.conf" %}
-{% include "_certificates.conf" %}
-{% include "_hsts.conf" %}
-{% include "_forced_tls.conf" %}
-{% include "_brotli.conf" %}
-
-include conf.d/include/always.conf;
-
-{{ advanced_config }}
-
-{% if use_default_location == 1 or use_default_location == true %}
- location / {
- include conf.d/include/always.conf;
- root /html/dead;
- try_files $uri /index.html;
- }
-{% endif %}
-
- # Custom
- include /data/nginx/custom/server_dead.conf;
-
-}
-{% endif %}
diff --git a/backend/templates/default.conf b/backend/templates/default.conf
deleted file mode 100644
index 365eb1562..000000000
--- a/backend/templates/default.conf
+++ /dev/null
@@ -1,60 +0,0 @@
-# ------------------------------------------------------------
-# Default Site
-# ------------------------------------------------------------
-server {
- listen 80 default_server;
- listen [::]:80 default_server;
-
- listen 443 ssl default_server;
- listen [::]:443 ssl default_server;
-
- listen 443 quic reuseport default_server;
- listen [::]:443 quic reuseport default_server;
- more_set_headers 'Alt-Svc: h3=":443"; ma=86400';
-
- server_name _;
-
- include conf.d/include/brotli.conf;
- include conf.d/include/force-tls.conf;
- include conf.d/include/tls-ciphers.conf;
- include conf.d/include/always.conf;
-
- ssl_certificate ;
- ssl_certificate_key ;
- #ssl_stapling_file ;
-
-{%- if value == "404" %}
- location / {
- include conf.d/include/always.conf;
- root /html/dead;
- try_files $uri /index.html;
- }
-{%- endif %}
-
-{%- if value == "444" %}
- return 444;
-{%- endif %}
-
-{%- if value == "redirect" %}
- location / {
- include conf.d/include/always.conf;
- return 307 {{ meta.redirect }};
- }
-{%- endif %}
-
-{%- if value == "congratulations" %}
- location / {
- include conf.d/include/always.conf;
- root /html/default;
- try_files $uri /index.html;
- }
-{%- endif %}
-
-{%- if value == "html" %}
- location / {
- include conf.d/include/always.conf;
- root /data/etc/html;
- try_files $uri /index.html;
- }
-{%- endif %}
-}
diff --git a/backend/templates/ip_ranges.conf b/backend/templates/ip_ranges.conf
deleted file mode 100644
index ee7db9359..000000000
--- a/backend/templates/ip_ranges.conf
+++ /dev/null
@@ -1,11 +0,0 @@
-{% for range in ip_ranges %}
-set_real_ip_from {{ range }};
-{% endfor %}
-
-map $http_cf_connecting_ip $real_ip {
- "" $http_x_real_ip;
- default $http_cf_connecting_ip;
-}
-
-more_set_input_headers "X-IP: $real_ip";
-real_ip_header X-IP;
diff --git a/backend/templates/proxy_host.conf b/backend/templates/proxy_host.conf
deleted file mode 100644
index a34aa9c0f..000000000
--- a/backend/templates/proxy_host.conf
+++ /dev/null
@@ -1,55 +0,0 @@
-{% include "_header_comment.conf" %}
-
-{% if enabled == 1 or enabled == true %}
-server {
- set $forward_scheme {{ forward_scheme }};
- set $server "{{ forward_host }}";
- set $port {{ forward_port }};
-
-{% include "_listen.conf" %}
-{% include "_certificates.conf" %}
-{% include "_hsts.conf" %}
-{% include "_forced_tls.conf" %}
-{% include "_brotli.conf" %}
-{% include "_access.conf" %}
-
- {% if block_exploits == 1 or block_exploits == true %}
- modsecurity on;
- {% if caching_enabled == 1 or caching_enabled == true -%}
- modsecurity_rules_file /usr/local/nginx/conf/conf.d/include/modsecurity-crs.conf;
- {% else %}
- modsecurity_rules_file /usr/local/nginx/conf/conf.d/include/modsecurity.conf;
- {% endif %}
- {% endif %}
-
- include conf.d/include/always.conf;
-
- {% if access_list_id > 0 %}
- {% if access_list.items.length > 0 %}
- {{ access_list.passauth }}
- {% endif %}
- {% endif %}
-
-{{ advanced_config }}
-
-# custom locations
-{{ locations }}
-
-{% if use_default_location == 1 or use_default_location == true %}
- location / {
- include conf.d/include/always.conf;
-
- {% if allow_websocket_upgrade == 1 or allow_websocket_upgrade == true %}
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection $connection_upgrade;
- {% endif %}
-
- # Proxy!
- include conf.d/include/proxy.conf;
- }
-{% endif %}
-
- # Custom
- include /data/nginx/custom/server_proxy.conf;
-}
-{% endif %}
diff --git a/backend/templates/redirection_host.conf b/backend/templates/redirection_host.conf
deleted file mode 100644
index 60030424b..000000000
--- a/backend/templates/redirection_host.conf
+++ /dev/null
@@ -1,29 +0,0 @@
-{% include "_header_comment.conf" %}
-
-{% if enabled == 1 or enabled == true %}
-server {
-{% include "_listen.conf" %}
-{% include "_certificates.conf" %}
-{% include "_hsts.conf" %}
-{% include "_forced_tls.conf" %}
-{% include "_brotli.conf" %}
-
-include conf.d/include/always.conf;
-
-{{ advanced_config }}
-
-{% if use_default_location == 1 or use_default_location == true %}
- location / {
- include conf.d/include/always.conf;
- {% if preserve_path == 1 or preserve_path == true %}
- return {{ forward_http_code }} {{ forward_scheme }}://{{ forward_domain_name }}$request_uri;
- {% else %}
- return {{ forward_http_code }} {{ forward_scheme }}://{{ forward_domain_name }};
- {% endif %}
- }
-{% endif %}
-
- # Custom
- include /data/nginx/custom/server_redirect.conf;
-}
-{% endif %}
diff --git a/backend/templates/stream.conf b/backend/templates/stream.conf
deleted file mode 100644
index 1e258adb9..000000000
--- a/backend/templates/stream.conf
+++ /dev/null
@@ -1,32 +0,0 @@
-# ------------------------------------------------------------
-# {{ incoming_port }} TCP: {{ tcp_forwarding }} UDP: {{ udp_forwarding }}
-# ------------------------------------------------------------
-
-{% if enabled == 1 or enabled == true %}
-
-{% if tcp_forwarding == 1 or tcp_forwarding == true -%}
-server {
- listen {{ incoming_port }};
- listen [::]:{{ incoming_port }};
-
- proxy_pass {{ forwarding_host }}:{{ forwarding_port }};
-
- # Custom
- include /data/nginx/custom/server_stream.conf;
- include /data/nginx/custom/server_stream_tcp.conf;
-}
-{% endif %}
-
-{% if udp_forwarding == 1 or udp_forwarding == true %}
-server {
- listen {{ incoming_port }} udp;
- listen [::]:{{ incoming_port }} udp;
- proxy_pass {{ forwarding_host }}:{{ forwarding_port }};
-
- # Custom
- include /data/nginx/custom/server_stream.conf;
- include /data/nginx/custom/server_stream_udp.conf;
-}
-{% endif %}
-
-{% endif %}
diff --git a/compose.yaml b/compose.yaml
index b2c6b32dc..e95941fef 100644
--- a/compose.yaml
+++ b/compose.yaml
@@ -6,13 +6,13 @@ services:
network_mode: host
volumes:
- "/opt/npm:/data"
-# - "/var/www:/var/www" # optional, if you want to use it as webserver for html/php
-# - "/opt/npm-letsencrypt:/etc/letsencrypt" # Only needed for first time migration from original nginx-proxy-manager to this fork
+ # - "/var/www:/var/www" # optional, if you want to use it as webserver for html/php
+ # - "/opt/npm-letsencrypt:/etc/letsencrypt" # Only needed for first time migration from original nginx-proxy-manager to this fork
environment:
- "TZ=Europe/Berlin" # set timezone, required
+# todo move to ui
# - "PUID=1000" # set group id, default 0 (root)
# - "PGID=1000" # set user id, default 0 (root), requires PUID
-# - "NIBEP=48694" # internal port of the NOMplus API, always bound to 127.0.0.1, default 48693, you need to change it, if you want to run multiple npm instances in network mode host
# - "GOAIWSP=48684" # internal port of goaccess, always bound to 127.0.0.1, default 48683, you need to change it, if you want to run multiple npm with goaccess instances in network mode host
# - "NPM_PORT=82" # Port the NPM UI should be bound to, default 81, you need to change it, if you want to run multiple npm instances in network mode host
# - "NPM_PORT=92" # Port the goaccess should be bound to, default 91, you need to change it, if you want to run multiple npm with goaccess instances in network mode host
diff --git a/darkmode.css b/darkmode.css
deleted file mode 100644
index 4215a75eb..000000000
--- a/darkmode.css
+++ /dev/null
@@ -1,247 +0,0 @@
-body {
- color: rgb(181, 175, 166) !important;
- background-color: rgb(28, 30, 31) !important;
-}
--webkit-scrollbar {
- background-color: #202324 !important;
- color: #aba499 !important;
-}
--webkit-scrollbar {
- background-color: #202324 !important;
- color: #aba499 !important;
-}
--webkit-scrollbar-thumb {
- background-color: #454a4d !important;
-}
-.avatar {
- background-color: rgb(48, 52, 54) !important;
- color: rgb(161, 152, 140) !important;
-}
-pre {
- color: rgb(195, 190, 182) !important;
- background-color: rgb(27, 29, 30) !important;
- text-shadow: rgb(24, 26, 27) 0px 1px !important;
-}
-.close {
- color: rgb(232, 230, 227) !important;
- text-shadow: rgb(24, 26, 27) 0px 1px 0px !important;
-}
-.form-fieldset {
- background-color: rgb(27, 30, 31) !important;
- border-color: rgb(53, 58, 60) !important;
-}
-.modal-content {
- background-color: rgb(24, 26, 27) !important;
- border-color: rgba(140, 130, 115, 0.2) !important;
-}
-.modal-header {
- border-bottom-color: rgb(53, 58, 60) !important;
-}
-.modal-footer {
- border-top-color: rgb(53, 58, 60) !important;
-}
-.alert-secondary {
- color: rgb(185, 179, 170) !important;
- background-color: rgb(37, 40, 41) !important;
- border-color: rgb(57, 62, 64) !important;
-}
-.nav-tabs {
- color: rgb(174, 167, 156) !important;
- border-bottom-color: rgb(56, 61, 63) !important;
-}
-.nav-tabs .nav-link.active,
-.nav-tabs .nav-item.show .nav-link {
- color: rgb(181, 175, 166) !important;
- background-color: rgb(28, 30, 31) !important;
- border-color: rgb(56, 61, 63) rgb(56, 61, 63) rgb(30, 46, 76) !important;
-}
-.nav-tabs .nav-link.active {
- border-color: rgb(35, 77, 136) !important;
- color: rgb(85, 151, 211) !important;
- background-color: transparent !important;
-}
-.selectize-input.focus {
- border-color: rgb(35, 77, 136) !important;
- box-shadow: rgba(39, 86, 151, 0.25) 0px 0px 0px 2px !important;
-}
-.selectgroup-input:checked + .selectgroup-button {
- border-color: rgb(35, 77, 136) !important;
- color: rgb(85, 151, 211) !important;
- background-color: rgb(30, 33, 34) !important;
-}
-.selectize-input,
-.selectize-control.single .selectize-input.input-active {
- background-color: rgb(24, 26, 27) !important;
-}
-
-.selectize-dropdown,
-.selectize-input,
-.selectize-input input {
- color: rgb(181, 175, 166) !important;
-}
-.selectize-input {
- border-color: rgba(124, 115, 101, 0.12) !important;
-}
-.selectize-input,
-.selectize-control.single .selectize-input.input-active {
- background-color: rgb(24, 26, 27) !important;
-}
-.selectize-control.multi .selectize-input div {
- background-color: rgb(35, 38, 39) !important;
- color: rgb(181, 175, 166) !important;
- border-color: rgba(124, 115, 101, 0.12) !important;
-}
-.selectize-dropdown,
-.selectize-input,
-.selectize-input input {
- color: #495057 !important;
- -webkit-font-smoothing: inherit !important;
-}
-.card {
- background-color: rgb(24, 26, 27) !important;
- border-color: rgba(124, 115, 101, 0.12) !important;
-}
-.tag {
- color: rgb(155, 146, 133) !important;
- background-color: rgb(35, 38, 39) !important;
-}
-.header {
- background-color: rgb(24, 26, 27) !important;
- border-bottom-color: rgba(124, 115, 101, 0.12) !important;
-}
-.navbar-light .navbar-brand {
- color: rgba(232, 230, 227, 0.9) !important;
-}
-.nav-tabs {
- color: rgb(174, 167, 156) !important;
-}
-.table th,
-.text-wrap table th,
-.table td,
-.text-wrap table td {
- border-top-color: rgb(56, 61, 63) !important;
-}
-.form-control {
- color: rgb(181, 175, 166) !important;
- background-color: rgb(24, 26, 27) !important;
- border-color: rgba(124, 115, 101, 0.12) !important;
-}
-.footer {
- background-color: rgb(24, 26, 27) !important;
- border-top-color: rgba(124, 115, 101, 0.12) !important;
- color: rgb(174, 167, 156) !important;
-}
-.text-default {
- color: rgb(181, 175, 166) !important;
-}
-.text-yellow {
- color: rgb(242, 202, 39) !important;
-}
-::selection {
- background-color: #004daa !important;
- color: #e8e6e3 !important;
-}
-.selection {
- background-color: #004daa !important;
- color: #e8e6e3 !important;
-}
-.dropdown-menu {
- color: rgb(181, 175, 166) !important;
- background-color: rgb(24, 26, 27) !important;
- border-color: rgba(124, 115, 101, 0.12) !important;
- box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px !important;
-}
-.dropdown-menu-arrow::before {
- border-right-color: transparent !important;
- border-left-color: transparent !important;
- border-bottom-color: rgba(84, 91, 95, 0.2) !important;
-}
-.dropdown-menu-arrow::after {
- border-right-color: transparent !important;
- border-bottom-color: rgb(48, 52, 54) !important;
- border-left-color: transparent !important;
-}
-.dropdown-divider {
- border-top-color: rgb(53, 58, 60) !important;
-}
-.dropdown-menu-arrowafter {
- border-right-color: transparent !important;
- border-bottom-color: rgb(48, 52, 54) !important;
- border-left-color: transparent !important;
-}
-.dropdown-item {
- color: rgb(155, 146, 133) !important;
-}
-.btn-secondary {
- color: rgb(181, 175, 166) !important;
- background-color: rgb(24, 26, 27) !important;
- border-color: rgba(124, 115, 101, 0.12) !important;
- box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 1px 0px !important;
- border-color: rgb(62 0 118 / 90%) !important;
-}
-.btn-teal {
- color: rgb(232, 230, 227) !important;
- background-color: rgb(34, 162, 149) !important;
- border-color: rgb(32, 150, 137) !important;
-}
-.stamp {
- color: rgb(232, 230, 227) !important;
-}
-.bg-yellow {
- background-color: rgb(144, 117, 8) !important;
-}
-.bg-blue {
- background-color: rgb(39, 86, 151) !important;
-}
-.bg-green {
- background-color: rgb(75, 149, 0) !important;
-}
-.bg-red {
- background-color: rgb(164, 26, 25) !important;
-}
-.custom-switch-indicator {
- background-color: rgb(35, 38, 39) !important;
- border-color: rgba(124, 115, 101, 0.12) !important;
-}
-.custom-switch-input:checked ~ .custom-switch-description {
- color: rgb(181, 175, 166) !important;
-}
-.custom-switch-input:checked ~ .custom-switch-indicator {
- background-color: rgb(34, 162, 149) !important;
-}
-.bg-success {
- background-color: rgb(75, 149, 0) !important;
-}
-.btn-success {
- color: rgb(232, 230, 227) !important;
- background-color: rgb(75, 149, 0) !important;
- border-color: rgb(101, 199, 0) !important;
-}
-.selectize-input.full {
- background-color: rgb(24, 26, 27) !important;
-}
-.selectize-input,
-.selectize-control.single .selectize-input.input-active {
- background-color: rgb(24, 26, 27) !important;
-}
-.selectize-dropdown,
-.selectize-input,
-.selectize-input input {
- color: rgb(181, 175, 166) !important;
-}
-.selectize-input {
- border-color: rgba(124, 115, 101, 0.12) !important;
-}
-.selectize-dropdown {
- color: rgb(202, 197, 190) !important;
- background-color: rgb(24, 26, 27) !important;
- border-right-color: rgb(61, 66, 69) !important;
- border-bottom-color: rgb(61, 66, 69) !important;
- border-left-color: rgb(61, 66, 69) !important;
- box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 3px !important;
-}
-.input-group-text {
- color: rgb(181, 175, 166) !important;
- background-color: rgb(26, 28, 29) !important;
- border-color: rgba(124, 115, 101, 0.12) !important;
-}
diff --git a/frontend/.gitignore b/frontend/.gitignore
deleted file mode 100644
index c8f4b4f98..000000000
--- a/frontend/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-dist
-node_modules
-webpack_stats.html
-yarn-error.log
diff --git a/frontend/app-images/default-avatar.jpg b/frontend/app-images/default-avatar.jpg
deleted file mode 100644
index 798871673..000000000
Binary files a/frontend/app-images/default-avatar.jpg and /dev/null differ
diff --git a/frontend/app-images/favicons/android-chrome-192x192.png b/frontend/app-images/favicons/android-chrome-192x192.png
deleted file mode 100644
index 69dc4caae..000000000
Binary files a/frontend/app-images/favicons/android-chrome-192x192.png and /dev/null differ
diff --git a/frontend/app-images/favicons/android-chrome-512x512.png b/frontend/app-images/favicons/android-chrome-512x512.png
deleted file mode 100644
index 6c69a48d8..000000000
Binary files a/frontend/app-images/favicons/android-chrome-512x512.png and /dev/null differ
diff --git a/frontend/app-images/favicons/apple-touch-icon.png b/frontend/app-images/favicons/apple-touch-icon.png
deleted file mode 100644
index 111caf46f..000000000
Binary files a/frontend/app-images/favicons/apple-touch-icon.png and /dev/null differ
diff --git a/frontend/app-images/favicons/browserconfig.xml b/frontend/app-images/favicons/browserconfig.xml
deleted file mode 100644
index 87c8dbd2f..000000000
--- a/frontend/app-images/favicons/browserconfig.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
- #333333
-
-
-
diff --git a/frontend/app-images/favicons/favicon-16x16.png b/frontend/app-images/favicons/favicon-16x16.png
deleted file mode 100644
index a456ed4f6..000000000
Binary files a/frontend/app-images/favicons/favicon-16x16.png and /dev/null differ
diff --git a/frontend/app-images/favicons/favicon-32x32.png b/frontend/app-images/favicons/favicon-32x32.png
deleted file mode 100644
index d8a8c7a1a..000000000
Binary files a/frontend/app-images/favicons/favicon-32x32.png and /dev/null differ
diff --git a/frontend/app-images/favicons/favicon.ico b/frontend/app-images/favicons/favicon.ico
deleted file mode 100644
index 9eae8b5f6..000000000
Binary files a/frontend/app-images/favicons/favicon.ico and /dev/null differ
diff --git a/frontend/app-images/favicons/mstile-150x150.png b/frontend/app-images/favicons/mstile-150x150.png
deleted file mode 100644
index 3586691f0..000000000
Binary files a/frontend/app-images/favicons/mstile-150x150.png and /dev/null differ
diff --git a/frontend/app-images/favicons/safari-pinned-tab.svg b/frontend/app-images/favicons/safari-pinned-tab.svg
deleted file mode 100644
index ab8289002..000000000
--- a/frontend/app-images/favicons/safari-pinned-tab.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/frontend/app-images/favicons/site.webmanifest b/frontend/app-images/favicons/site.webmanifest
deleted file mode 100644
index 99d1016eb..000000000
--- a/frontend/app-images/favicons/site.webmanifest
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "name": "",
- "short_name": "",
- "icons": [
- {
- "src": "/images/favicons/android-chrome-192x192.png",
- "sizes": "192x192",
- "type": "image/png"
- },
- {
- "src": "/images/favicons/android-chrome-512x512.png",
- "sizes": "512x512",
- "type": "image/png"
- }
- ],
- "theme_color": "#ffffff",
- "background_color": "#ffffff",
- "display": "standalone"
-}
diff --git a/frontend/app-images/logo-256.png b/frontend/app-images/logo-256.png
deleted file mode 100644
index 0ea6dd3d4..000000000
Binary files a/frontend/app-images/logo-256.png and /dev/null differ
diff --git a/frontend/app-images/logo-text-vertical-grey.png b/frontend/app-images/logo-text-vertical-grey.png
deleted file mode 100644
index 03010d7fe..000000000
Binary files a/frontend/app-images/logo-text-vertical-grey.png and /dev/null differ
diff --git a/frontend/fonts/feather b/frontend/fonts/feather
deleted file mode 120000
index 440203ba2..000000000
--- a/frontend/fonts/feather
+++ /dev/null
@@ -1 +0,0 @@
-../node_modules/tabler-ui/dist/assets/fonts/feather
\ No newline at end of file
diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff
deleted file mode 100644
index 96d8768ea..000000000
Binary files a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff and /dev/null differ
diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff2 b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff2
deleted file mode 100644
index e97a22181..000000000
Binary files a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff2 and /dev/null differ
diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff
deleted file mode 100644
index 0829caef1..000000000
Binary files a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff and /dev/null differ
diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff2 b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff2
deleted file mode 100644
index 7c901cd84..000000000
Binary files a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff2 and /dev/null differ
diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff
deleted file mode 100644
index 99652481a..000000000
Binary files a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff and /dev/null differ
diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff2 b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff2
deleted file mode 100644
index 343e5ba8d..000000000
Binary files a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff2 and /dev/null differ
diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff
deleted file mode 100644
index 92c3260ef..000000000
Binary files a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff and /dev/null differ
diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff2 b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff2
deleted file mode 100644
index d552543be..000000000
Binary files a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff2 and /dev/null differ
diff --git a/frontend/html/index.ejs b/frontend/html/index.ejs
deleted file mode 100644
index 3af1902c2..000000000
--- a/frontend/html/index.ejs
+++ /dev/null
@@ -1,9 +0,0 @@
-<% var title = 'NPMplus' %>
-<%- include partials/header.ejs %>
-
-
-
-
-
-
-<%- include partials/footer.ejs %>
diff --git a/frontend/html/login.ejs b/frontend/html/login.ejs
deleted file mode 100644
index 2fb2b1193..000000000
--- a/frontend/html/login.ejs
+++ /dev/null
@@ -1,9 +0,0 @@
-<% var title = 'Login – NPMplus' %>
-<%- include partials/header.ejs %>
-
-
-
-
-
-
-<%- include partials/footer.ejs %>
diff --git a/frontend/html/partials/footer.ejs b/frontend/html/partials/footer.ejs
deleted file mode 100644
index 7fb2bd61b..000000000
--- a/frontend/html/partials/footer.ejs
+++ /dev/null
@@ -1,2 +0,0 @@
-