Compare commits

..

25 Commits

Author SHA1 Message Date
0bc12f3bdf Merge from develop v2.0.14 release ()
* Selection of the ssl protocols/ciphers is done using the default server block. ()
* Fix wrap for tags ()
2019-09-04 14:51:24 +10:00
31aa9c9644 Allow including custom nginx conf files ()
* Allow including custom nginx conf files

Give advanced users more flexibility by allowing them to include custom config files at differents locations in the nginx configuration.

`/data/nginx/custom/root.conf`: Included at the very end of nginx.conf
`/data/nginx/custom/http.conf`: Included at the end of the main `http` block
`/data/nginx/custom/server_proxy.conf`: Included at the end of every proxy `server` block
`/data/nginx/custom/server_redirect.conf`: Included at the end of every redirection `server` block
`/data/nginx/custom/server_stream.conf`: Included at the end of every stream `server` block
`/data/nginx/custom/server_stream_tcp.conf`: Included at the end of every TCP stream `server` block
`/data/nginx/custom/server_stream_udp.conf`: Included at the end of every UDP stream `server` block

* Don't fail if file doesn't exist

* Advanced Nginx settings doc
2019-08-09 11:19:42 +10:00
ddbfdf6f6e Open up lets Encrypt acme challenge config ()
Since Lets Encrypt don't publish IP ranges that their acme challenge service will be sourced from, we need to allow free access to this location special to override any IP ACLs added by Advanced Custom Nginx Configuration. Due to the way Nginx config is applied, this only applies to the regex and below, keeping the IP ACLs working for the rest of the website.
2019-07-05 08:32:41 +10:00
43c7063538 Center username vertically ()
Center username/role container vertically within header
2019-06-25 15:25:45 +10:00
3f089fb239 Updated documentation, installation instructions and examples 2019-05-10 15:31:25 +10:00
2d0f7d5126 Updated documentation, installation instructions and examples 2019-05-10 15:26:12 +10:00
06272d3d2c Use correct var when returning updated certificate 2019-05-09 10:03:41 +10:00
3885c0ad6d Add cert renewals to audit log 2019-05-09 09:20:49 +10:00
099ec00155 Don't use LE staging when debug mode is on in production 2019-05-09 08:58:10 +10:00
92fcae9c54 Added missing dialog for renewing certs 2019-05-08 15:34:14 +10:00
22e8961c80 Fixes - allow using / location in custom location 2019-05-08 15:33:54 +10:00
4d5adefa41 Added ability to force renew a LE cert, and also fix revoking certs 2019-05-08 15:25:48 +10:00
feaa0e51bd Removed use strict 2019-05-08 15:24:57 +10:00
af83cb57d0 Merge branch 'develop' of github.com:jc21/nginx-proxy-manager into develop 2019-05-08 11:23:50 +10:00
8b4f3507c3 Revert to previous tabler version to hopefully fix ui issues 2019-05-08 11:21:59 +10:00
bda3dba369 Revert to previous tabler version to hopefully fix ui issues 2019-05-08 10:53:44 +10:00
beb313af40 Merge branch 'master' of github.com:jc21/nginx-proxy-manager into develop 2019-05-08 10:11:32 +10:00
4fad9d672f Correcting X-XSS-Protection Header ()
* Correcting X-XSS-Protection Header

X-XSS-Protection sets the configuration for the cross-site scripting filters built into most browsers. The best configuration is "X-XSS-Protection: 1; mode=block".

Was "0"
Now "1; mode=block"

* Update issue templates
2019-05-08 10:11:05 +10:00
0fca64929e Try DNS challenge in addition to http () 2019-05-08 10:07:43 +10:00
9e476e5b24 Only Secure TLS Ciphers & Protocols ()
Disable insecure SSL/TLS ciphers & protocols. Only TLS_1.2 and TLS_1.3 should be enabled.
2019-05-08 10:01:08 +10:00
0819a265f5 Bumped version 2019-05-08 09:50:20 +10:00
ad8eac4f07 Update issue templates 2019-05-08 09:36:44 +10:00
b49de0e23e Enable TLS 1.3 by default 2019-05-02 13:03:16 +10:00
efbd024da9 Update copyright year ()
Updated the year in the copyright statement in the footer
2019-04-20 21:28:06 +10:00
e7ddcb91fc Fixed directory traversal vulnerability. ()
Awesome find!
2019-04-03 08:37:40 +10:00
161 changed files with 445 additions and 451 deletions
.github/ISSUE_TEMPLATE
README.md
doc
docker-compose.ymlpackage.json
rootfs/etc/nginx
src
backend
frontend

36
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file

@ -0,0 +1,36 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Checklist**
- Have you pulled and found the error with `jc21/nginx-proxy-manager:latest` docker image?
- Are you sure you're not using someone else's docker image?
- If having problems with Lets Encrypt, have you made absolutely sure your site is accessible from outside of your network?
**Describe the bug**
- A clear and concise description of what the bug is.
- What version of Nginx Proxy Manager is reported on the login page?
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Operating System**
- Please specify if using a Rpi, Mac, orchestration tool or any other setups that might affect the reproduction of this error.
**Additional context**
Add any other context about the problem here, docker version, browser version if applicable to the problem. Too much info is better than too little.

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

@ -2,7 +2,7 @@
# Nginx Proxy Manager
![Version](https://img.shields.io/badge/version-2.0.12-green.svg?style=for-the-badge)
![Version](https://img.shields.io/badge/version-2.0.13-green.svg?style=for-the-badge)
![Stars](https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge)
![Pulls](https://img.shields.io/docker/pulls/jc21/nginx-proxy-manager.svg?style=for-the-badge)

17
doc/ADVANCED_NGINX.md Normal file

@ -0,0 +1,17 @@
## Advanced Nginx Configuration
If you are a more advanced user, you might be itching for extra Nginx customizability.
NPM has the ability to include different custom configuration snippets in different places.
You can add your custom configuration snippet files at `/data/nginx/custom` as follow:
`/data/nginx/custom/root.conf`: Included at the very end of nginx.conf
`/data/nginx/custom/http.conf`: Included at the end of the main http block
`/data/nginx/custom/server_proxy.conf`: Included at the end of every proxy server block
`/data/nginx/custom/server_redirect.conf`: Included at the end of every redirection server block
`/data/nginx/custom/server_stream.conf`: Included at the end of every stream server block
`/data/nginx/custom/server_stream_tcp.conf`: Included at the end of every TCP stream server block
`/data/nginx/custom/server_stream_udp.conf`: Included at the end of every UDP stream server block
Every file is optional.

@ -1,8 +1,8 @@
![Nginx Proxy Manager](https://public.jc21.com/nginx-proxy-manager/github.png "Nginx Proxy Manager")
# Nginx Proxy Manager
# [Nginx Proxy Manager](https://nginxproxymanager.jc21.com)
![Version](https://img.shields.io/badge/version-2.0.12-green.svg?style=for-the-badge)
![Version](https://img.shields.io/badge/version-2.0.13-green.svg?style=for-the-badge)
![Stars](https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge)
![Pulls](https://img.shields.io/docker/pulls/jc21/nginx-proxy-manager.svg?style=for-the-badge)
@ -15,16 +15,18 @@ running at home or otherwise, including free SSL, without having to know too muc
## Tags
* latest 2, 2.x.x ([Dockerfile](https://github.com/jc21/nginx-proxy-manager/blob/master/Dockerfile))
* latest-armhf, 2-armhf, 2.x.x-armhf ([Dockerfile](https://github.com/jc21/nginx-proxy-manager/blob/master/Dockerfile.armhf))
* latest-arm64, 2-arm64, 2.x.x-arm64 ([Dockerfile](https://github.com/jc21/nginx-proxy-manager/blob/master/Dockerfile.arm64))
* 1, 1.x.x ([Dockerfile](https://github.com/jc21/nginx-proxy-manager/blob/1.1.2/Dockerfile))
* 1-armhf, 1.x.x-armhf ([Dockerfile](https://github.com/jc21/nginx-proxy-manager/blob/1.1.2/Dockerfile.armhf))
* latest-arm7l, 2-arm7l, 2.x.x-arm7l ([Dockerfile](https://github.com/jc21/nginx-proxy-manager/blob/master/Dockerfile.arm7l))
## Getting started
Please consult the [installation instructions](https://github.com/jc21/nginx-proxy-manager/blob/master/doc/INSTALL.md) for a complete guide or
if you just want to get up and running in the quickest time possible, grab all the files in the [doc/example/](https://github.com/jc21/nginx-proxy-manager/tree/master/doc/example) folder and run `docker-compose up -d`
if you just want to get up and running in the quickest time possible, grab all the files in the [doc/example/](https://github.com/jc21/nginx-proxy-manager/tree/master/doc/example) folder and run:
```bash
docker-compose up -d
```
## Screenshots

@ -1,9 +1,13 @@
## Installation and Configuration
There's a few ways to configure this app depending on:
If you just want to get up and running in the quickest time possible, grab all the files in
the [doc/example/](https://github.com/jc21/nginx-proxy-manager/tree/master/doc/example)
folder and run:
```bash
docker-compose up -d
```
- Whether you use `docker-compose` or vanilla docker
- Which architecture you're running it on (raspberry pi also supported - Testers wanted!)
### Configuration File
@ -13,22 +17,22 @@ Don't worry, this is easy to do.
The app requires a configuration file to let it know what database you're using.
Here's an example configuration for `mysql` (or mariadb):
Here's an example configuration for `mysql` (or mariadb) that is compatible with the docker-compose example below:
```json
{
"database": {
"engine": "mysql",
"host": "127.0.0.1",
"name": "nginxproxymanager",
"user": "nginxproxymanager",
"password": "password123",
"host": "db",
"name": "npm",
"user": "npm",
"password": "npm",
"port": 3306
}
}
```
Once you've created your configuration file it's easy to mount it in the docker container, examples below.
Once you've created your configuration file it's easy to mount it in the docker container.
**Note:** After the first run of the application, the config file will be altered to include generated encryption keys unique to your installation. These keys
affect the login and session management of the application. If these keys change for any reason, all users will be logged out.
@ -36,37 +40,13 @@ affect the login and session management of the application. If these keys change
### Database
This app doesn't come with a database, you have to provide one yourself. Currently only `mysql/mariadb` is supported.
This app doesn't come with a database, you have to provide one yourself. Currently only `mysql/mariadb` is supported for the minimum versions:
It's easy to use another docker container for your database also and link it as part of the docker stack. Here's an example:
- MySQL v5.7.8+
- MariaDB v10.2.7+
```yml
version: "3"
services:
app:
image: jc21/nginx-proxy-manager:latest
restart: always
ports:
- 80:80
- 81:81
- 443:443
volumes:
- ./config.json:/app/config/production.json
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
depends_on:
- db
db:
image: jc21/mariadb-aria
restart: always
environment:
MYSQL_ROOT_PASSWORD: "password123"
MYSQL_DATABASE: "nginxproxymanager"
MYSQL_USER: "nginxproxymanager"
MYSQL_PASSWORD: "password123"
volumes:
- ./data/mysql:/var/lib/mysql
```
It's easy to use another docker container for your database also and link it as part of the docker stack, so that's what the following examples
are going to use.
### Running the App
@ -80,48 +60,51 @@ services:
image: jc21/nginx-proxy-manager:latest
restart: always
ports:
# Public HTTP Port:
- 80:80
- 81:81
# Public HTTPS Port:
- 443:443
# Admin Web Port:
- 81:81
volumes:
# Make sure this config.json file exists as per instructions above:
- ./config.json:/app/config/production.json
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
depends_on:
- db
db:
image: mariadb
restart: always
environment:
MYSQL_ROOT_PASSWORD: "npm"
MYSQL_DATABASE: "npm"
MYSQL_USER: "npm"
MYSQL_PASSWORD: "npm"
volumes:
- ./data/mysql:/var/lib/mysql
```
Vanilla Docker:
Then:
```bash
docker run -d \
--name nginx-proxy-manager \
-p 80:80 \
-p 81:81 \
-p 443:443 \
-v /path/to/config.json:/app/config/production.json \
-v /path/to/data:/data \
-v /path/to/letsencrypt:/etc/letsencrypt \
jc21/nginx-proxy-manager:latest
docker-compose up -d
```
### Running on Raspberry PI / `armhf`
### Running on Raspberry PI / ARM devices
I have created `armhf` and `arm64` docker containers just for you. There may be issues with it,
if you have issues please report them here.
There are docker images for all versions of the Rasberry Pi with the exception of the really old `armv6l` versions.
Note: Rpi v2 and below won't work with these images.
The `latest` docker image is a manifest of all the different architecture docker builds supported, so this means
you don't have to worry about doing anything special and you can follow the common instructions above.
```bash
docker run -d \
--name nginx-proxy-manager-app \
-p 80:80 \
-p 81:81 \
-p 443:443 \
-v /path/to/config.json:/app/config/production.json \
-v /path/to/data:/data \
-v /path/to/letsencrypt:/etc/letsencrypt \
jc21/nginx-proxy-manager:latest-armhf
```
Check out the [dockerhub tags](https://cloud.docker.com/repository/registry-1.docker.io/jc21/nginx-proxy-manager/tags)
for a list of supported architectures and if you want one that doesn't exist,
[create a feature request](https://github.com/jc21/nginx-proxy-manager/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=).
Also, if you don't know how to already, follow [this guide to install docker and docker-compose](https://manre-universe.net/how-to-run-docker-and-docker-compose-on-raspbian/)
on Raspbian.
### Initial Run
@ -162,4 +145,3 @@ value by specifying it as a Docker environment variable. The default if not spec
```
... -e "X_FRAME_OPTIONS=sameorigin" ...
```

@ -2,9 +2,9 @@
"database": {
"engine": "mysql",
"host": "db",
"name": "nginxproxymanager",
"user": "nginxproxymanager",
"password": "password123",
"name": "npm",
"user": "npm",
"password": "npm",
"port": 3306
}
}

@ -1,7 +1,7 @@
version: "3"
services:
app:
image: jc21/nginx-proxy-manager:2
image: jc21/nginx-proxy-manager:latest
restart: always
ports:
- 80:80
@ -17,12 +17,12 @@ services:
# if you want pretty colors in your docker logs:
- FORCE_COLOR=1
db:
image: jc21/mariadb-aria
image: mariadb:latest
restart: always
environment:
MYSQL_ROOT_PASSWORD: "password123"
MYSQL_DATABASE: "nginxproxymanager"
MYSQL_USER: "nginxproxymanager"
MYSQL_PASSWORD: "password123"
MYSQL_ROOT_PASSWORD: "npm"
MYSQL_DATABASE: "npm"
MYSQL_USER: "npm"
MYSQL_PASSWORD: "npm"
volumes:
- ./data/mysql:/var/lib/mysql

@ -4,9 +4,9 @@ services:
app:
image: jc21/nginx-proxy-manager-base:latest
ports:
- 8080:80
- 8081:81
- 8443:443
- 80:80
- 81:81
- 43:443
environment:
- NODE_ENV=development
- FORCE_COLOR=1
@ -22,7 +22,7 @@ services:
- db
command: node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js
db:
image: mariadb:10.3.7
image: jc21/mariadb-aria
environment:
MYSQL_ROOT_PASSWORD: "npm"
MYSQL_DATABASE: "npm"

@ -1,6 +1,6 @@
{
"name": "nginx-proxy-manager",
"version": "2.0.12",
"version": "2.0.14",
"description": "A beautiful interface for creating Nginx endpoints",
"main": "src/backend/index.js",
"devDependencies": {
@ -28,7 +28,7 @@
"numeral": "^2.0.6",
"sass-loader": "^7.0.3",
"style-loader": "^0.22.1",
"tabler-ui": "git+https://github.com/tabler/tabler.git",
"tabler-ui": "git+https://github.com/tabler/tabler.git#00f78ad823311bc3ad974ac3e5b0126198f0a813",
"underscore": "^1.8.3",
"webpack": "^4.25.1",
"webpack-cli": "^3.1.2",

@ -47,7 +47,7 @@ server {
ssl_certificate /data/nginx/dummycert.pem;
ssl_certificate_key /data/nginx/dummykey.pem;
ssl_ciphers aNULL;
include conf.d/include/ssl-ciphers.conf;
return 444;
}

@ -2,7 +2,10 @@
# We use ^~ here, so that we don't check other regexes (for speed-up). We actually MUST cancel
# other regex checks, because in our other config files have regex rule that denies access to files with dotted names.
location ^~ /.well-known/acme-challenge/ {
# Since this is for letsencrypt authentication of a domain and they do not give IP ranges of their infrastructure
# we need to open up access by turning off auth and IP ACL for this location.
auth_basic off;
allow all;
# Set correct content type. According to this:
# https://community.letsencrypt.org/t/using-the-webroot-domain-verification-method/1445/29

@ -2,8 +2,8 @@ ssl_session_timeout 5m;
ssl_session_cache shared:SSL:50m;
# intermediate configuration. tweak to your needs.
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'EECDH+AESGCM:AES256+EECDH:AES256+EDH:EDH+AESGCM:ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-
ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AE
S128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
S128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES';
ssl_prefer_server_ciphers on;

@ -76,6 +76,9 @@ http {
include /data/nginx/redirection_host/*.conf;
include /data/nginx/dead_host/*.conf;
include /data/nginx/temp/*.conf;
# Custom
include /data/nginx/custom/http[.]conf;
}
stream {
@ -83,3 +86,5 @@ stream {
include /data/nginx/stream/*.conf;
}
# Custom
include /data/nginx/custom/root[.]conf;

@ -1,5 +1,3 @@
'use strict';
const path = require('path');
const express = require('express');
const bodyParser = require('body-parser');
@ -48,7 +46,7 @@ app.use(function (req, res, next) {
res.set({
'Strict-Transport-Security': 'includeSubDomains; max-age=631138519; preload',
'X-XSS-Protection': '0',
'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',

@ -1,5 +1,3 @@
'use strict';
const config = require('config');
if (!config.has('database')) {

@ -1,10 +1,8 @@
'use strict';
const fs = require('fs');
const logger = require('./logger').import;
const utils = require('./lib/utils');
const batchflow = require('batchflow');
const debug_mode = process.env.NODE_ENV !== 'production';
const debug_mode = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG;
const internalProxyHost = require('./internal/proxy-host');
const internalRedirectionHost = require('./internal/redirection-host');

@ -1,5 +1,3 @@
'use strict';
const _ = require('lodash');
const fs = require('fs');
const batchflow = require('batchflow');

@ -1,5 +1,3 @@
'use strict';
const error = require('../lib/error');
const auditLogModel = require('../models/audit-log');
@ -46,9 +44,9 @@ const internalAuditLog = {
* @param {Access} access
* @param {Object} data
* @param {String} data.action
* @param {Integer} [data.user_id]
* @param {Integer} [data.object_id]
* @param {Integer} [data.object_type]
* @param {Number} [data.user_id]
* @param {Number} [data.object_id]
* @param {Number} [data.object_type]
* @param {Object} [data.meta]
* @returns {Promise}
*/

@ -1,5 +1,3 @@
'use strict';
const fs = require('fs');
const _ = require('lodash');
const logger = require('../logger').ssl;
@ -9,19 +7,20 @@ const internalAuditLog = require('./audit-log');
const tempWrite = require('temp-write');
const utils = require('../lib/utils');
const moment = require('moment');
const debug_mode = process.env.NODE_ENV !== 'production';
const debug_mode = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG;
const le_staging = process.env.NODE_ENV !== 'production';
const internalNginx = require('./nginx');
const internalHost = require('./host');
const certbot_command = '/usr/bin/certbot';
function omissions () {
function omissions() {
return ['is_deleted'];
}
const internalCertificate = {
allowed_ssl_files: ['certificate', 'certificate_key', 'intermediate_certificate'],
interval_timeout: 1000 * 60 * 60 * 12, // 12 hours
interval_timeout: 1000 * 60 * 60, // 1 hour
interval: null,
interval_processing: false,
@ -38,7 +37,7 @@ const internalCertificate = {
internalCertificate.interval_processing = true;
logger.info('Renewing SSL certs close to expiry...');
return utils.exec(certbot_command + ' renew -q ' + (debug_mode ? '--staging' : ''))
return utils.exec(certbot_command + ' renew -q ' + (le_staging ? '--staging' : ''))
.then(result => {
logger.info(result);
@ -205,7 +204,7 @@ const internalCertificate = {
/**
* @param {Access} access
* @param {Object} data
* @param {Integer} data.id
* @param {Number} data.id
* @param {String} [data.email]
* @param {String} [data.name]
* @return {Promise}
@ -251,7 +250,7 @@ const internalCertificate = {
/**
* @param {Access} access
* @param {Object} data
* @param {Integer} data.id
* @param {Number} data.id
* @param {Array} [data.expand]
* @param {Array} [data.omit]
* @return {Promise}
@ -297,7 +296,7 @@ const internalCertificate = {
/**
* @param {Access} access
* @param {Object} data
* @param {Integer} data.id
* @param {Number} data.id
* @param {String} [data.reason]
* @returns {Promise}
*/
@ -381,7 +380,7 @@ const internalCertificate = {
/**
* Report use
*
* @param {Integer} user_id
* @param {Number} user_id
* @param {String} visibility
* @returns {Promise}
*/
@ -522,7 +521,7 @@ const internalCertificate = {
/**
* @param {Access} access
* @param {Object} data
* @param {Integer} data.id
* @param {Number} data.id
* @param {Object} data.files
* @returns {Promise}
*/
@ -719,9 +718,9 @@ const internalCertificate = {
let cmd = certbot_command + ' certonly --cert-name "npm-' + certificate.id + '" --agree-tos ' +
'--email "' + certificate.meta.letsencrypt_email + '" ' +
'--preferred-challenges "http" ' +
'--preferred-challenges "dns,http" ' +
'-n -a webroot -d "' + certificate.domain_names.join(',') + '" ' +
(debug_mode ? '--staging' : '');
(le_staging ? '--staging' : '');
if (debug_mode) {
logger.info('Command:', cmd);
@ -734,6 +733,48 @@ const internalCertificate = {
});
},
/**
* @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') {
return internalCertificate.renewLetsEncryptSsl(certificate)
.then(() => {
return internalCertificate.getCertificateInfoFromFile('/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem')
})
.then(cert_info => {
return certificateModel
.query()
.patchAndFetchById(certificate.id, {
expires_on: certificateModel.raw('FROM_UNIXTIME(' + cert_info.dates.to + ')')
});
})
.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 Let\'sEncrypt certificates can be renewed');
}
})
},
/**
* @param {Object} certificate the certificate row
* @returns {Promise}
@ -741,7 +782,7 @@ const internalCertificate = {
renewLetsEncryptSsl: certificate => {
logger.info('Renewing Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
let cmd = certbot_command + ' renew -n --force-renewal --disable-hook-validation --cert-name "npm-' + certificate.id + '" ' + (debug_mode ? '--staging' : '');
let cmd = certbot_command + ' renew -n --force-renewal --disable-hook-validation --cert-name "npm-' + certificate.id + '" ' + (le_staging ? '--staging' : '');
if (debug_mode) {
logger.info('Command:', cmd);
@ -762,17 +803,29 @@ const internalCertificate = {
revokeLetsEncryptSsl: (certificate, throw_errors) => {
logger.info('Revoking Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
let cmd = certbot_command + ' revoke --cert-path "/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem" ' + (debug_mode ? '--staging' : '');
let revoke_cmd = certbot_command + ' revoke --cert-path "/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem" ' + (le_staging ? '--staging' : '');
let delete_cmd = certbot_command + ' delete --cert-name "npm-' + certificate.id + '" ' + (le_staging ? '--staging' : '');
if (debug_mode) {
logger.info('Command:', cmd);
logger.info('Command:', revoke_cmd);
}
return utils.exec(cmd)
.then(result => {
return utils.exec(revoke_cmd)
.then((result) => {
logger.info(result);
return result;
})
.then(() => {
if (debug_mode) {
logger.info('Command:', delete_cmd);
}
return utils.exec(delete_cmd)
.then((result) => {
logger.info(result);
return result;
})
})
.catch(err => {
if (debug_mode) {
logger.error(err.message);
@ -796,7 +849,7 @@ const internalCertificate = {
/**
* @param {Object} in_use_result
* @param {Integer} in_use_result.total_count
* @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
@ -826,7 +879,7 @@ const internalCertificate = {
/**
* @param {Object} in_use_result
* @param {Integer} in_use_result.total_count
* @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

@ -1,5 +1,3 @@
'use strict';
const _ = require('lodash');
const error = require('../lib/error');
const deadHostModel = require('../models/dead_host');

@ -1,8 +1,5 @@
'use strict';
const https = require('https');
const fs = require('fs');
const _ = require('lodash');
const logger = require('../logger').ip_ranges;
const error = require('../lib/error');
const internalNginx = require('./nginx');

@ -4,7 +4,7 @@ const Liquid = require('liquidjs');
const logger = require('../logger').nginx;
const utils = require('../lib/utils');
const error = require('../lib/error');
const debug_mode = process.env.NODE_ENV !== 'production';
const debug_mode = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG;
const internalNginx = {
@ -162,7 +162,7 @@ const internalNginx = {
renderedLocations += await renderer.parseAndRender(template, locationCopy);
}
}
};
locationRendering().then(() => resolve(renderedLocations));
});
@ -211,6 +211,14 @@ const internalNginx = {
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();
}

@ -25,7 +25,7 @@ const internalProxyHost = {
}
return access.can('proxy_hosts:create', data)
.then(access_data => {
.then(() => {
// Get a list of the domain names and check each of them against existing records
let domain_name_check_promises = [];
@ -52,7 +52,7 @@ const internalProxyHost = {
.omit(omissions())
.insertAndFetch(data);
})
.then(row => {
.then((row) => {
if (create_certificate) {
return internalCertificate.createQuickCertificate(access, data)
.then(cert => {
@ -69,21 +69,21 @@ const internalProxyHost = {
return row;
}
})
.then(row => {
.then((row) => {
// re-fetch with cert
return internalProxyHost.get(access, {
id: row.id,
expand: ['certificate', 'owner', 'access_list']
});
})
.then(row => {
.then((row) => {
// Configure nginx
return internalNginx.configure(proxyHostModel, 'proxy_host', row)
.then(() => {
return row;
});
})
.then(row => {
.then((row) => {
// Audit log
data.meta = _.assign({}, data.meta || {}, row.meta);

@ -1,5 +1,3 @@
'use strict';
const _ = require('lodash');
const error = require('../lib/error');
const redirectionHostModel = require('../models/redirection_host');

@ -1,5 +1,3 @@
'use strict';
const internalProxyHost = require('./proxy-host');
const internalRedirectionHost = require('./redirection-host');
const internalDeadHost = require('./dead-host');

@ -1,5 +1,3 @@
'use strict';
const _ = require('lodash');
const error = require('../lib/error');
const streamModel = require('../models/stream');

@ -1,5 +1,3 @@
'use strict';
const _ = require('lodash');
const error = require('../lib/error');
const userModel = require('../models/user');

@ -1,5 +1,3 @@
'use strict';
const _ = require('lodash');
const error = require('../lib/error');
const userModel = require('../models/user');

@ -1,5 +1,3 @@
'use strict';
/**
* Some Notes: This is a friggin complicated piece of code.
*

@ -1,5 +1,3 @@
'use strict';
const _ = require('lodash');
const util = require('util');

@ -1,5 +1,3 @@
'use strict';
const validator = require('../validator');
module.exports = function (req, res, next) {

@ -1,5 +1,3 @@
'use strict';
const Access = require('../access');
module.exports = () => {

@ -1,5 +1,3 @@
'use strict';
module.exports = function () {
return function (req, res, next) {
if (req.headers.authorization) {

@ -1,5 +1,3 @@
'use strict';
let _ = require('lodash');
module.exports = function (default_sort, default_offset, default_limit, max_limit) {

@ -1,5 +1,3 @@
'use strict';
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;

@ -1,7 +1,4 @@
'use strict';
const moment = require('moment');
const _ = require('lodash');
module.exports = {

@ -1,5 +1,3 @@
'use strict';
const migrate_name = 'identifier_for_migrate';
const logger = require('../logger').migrate;

@ -1,5 +1,3 @@
'use strict';
const exec = require('child_process').exec;
module.exports = {

@ -1,5 +1,3 @@
'use strict';
const error = require('../error');
const path = require('path');
const parser = require('json-schema-ref-parser');

@ -1,5 +1,3 @@
'use strict';
const _ = require('lodash');
const error = require('../error');
const definitions = require('../../schema/definitions.json');

@ -1,5 +1,3 @@
'use strict';
const db = require('./db');
const logger = require('./logger').migrate;

@ -1,5 +1,3 @@
'use strict';
const migrate_name = 'initial-schema';
const logger = require('../logger').migrate;

@ -1,5 +1,3 @@
'use strict';
const migrate_name = 'websockets';
const logger = require('../logger').migrate;

@ -1,5 +1,3 @@
'use strict';
const migrate_name = 'forward_host';
const logger = require('../logger').migrate;

@ -1,5 +1,3 @@
'use strict';
const migrate_name = 'http2_support';
const logger = require('../logger').migrate;

@ -1,5 +1,3 @@
'use strict';
const migrate_name = 'forward_scheme';
const logger = require('../logger').migrate;

@ -1,5 +1,3 @@
'use strict';
const migrate_name = 'disabled';
const logger = require('../logger').migrate;

@ -1,5 +1,3 @@
'use strict';
const migrate_name = 'custom_locations';
const logger = require('../logger').migrate;

@ -1,5 +1,3 @@
'use strict';
const migrate_name = 'hsts';
const logger = require('../logger').migrate;

@ -1,8 +1,6 @@
// Objection Docs:
// http://vincit.github.io/objection.js/
'use strict';
const db = require('../db');
const Model = require('objection').Model;
const User = require('./user');

@ -1,8 +1,6 @@
// Objection Docs:
// http://vincit.github.io/objection.js/
'use strict';
const db = require('../db');
const Model = require('objection').Model;

@ -1,8 +1,6 @@
// Objection Docs:
// http://vincit.github.io/objection.js/
'use strict';
const db = require('../db');
const Model = require('objection').Model;
const User = require('./user');

@ -1,8 +1,6 @@
// Objection Docs:
// http://vincit.github.io/objection.js/
'use strict';
const bcrypt = require('bcrypt');
const db = require('../db');
const Model = require('objection').Model;

@ -1,8 +1,6 @@
// Objection Docs:
// http://vincit.github.io/objection.js/
'use strict';
const db = require('../db');
const Model = require('objection').Model;
const User = require('./user');

@ -1,8 +1,6 @@
// Objection Docs:
// http://vincit.github.io/objection.js/
'use strict';
const db = require('../db');
const Model = require('objection').Model;
const User = require('./user');

@ -1,8 +1,6 @@
// Objection Docs:
// http://vincit.github.io/objection.js/
'use strict';
const db = require('../db');
const Model = require('objection').Model;
const User = require('./user');

@ -1,8 +1,6 @@
// Objection Docs:
// http://vincit.github.io/objection.js/
'use strict';
const db = require('../db');
const Model = require('objection').Model;
const User = require('./user');

@ -1,8 +1,6 @@
// Objection Docs:
// http://vincit.github.io/objection.js/
'use strict';
const db = require('../db');
const Model = require('objection').Model;
const User = require('./user');

@ -3,8 +3,6 @@
and then has abilities after that.
*/
'use strict';
const _ = require('lodash');
const config = require('config');
const jwt = require('jsonwebtoken');

@ -1,8 +1,6 @@
// Objection Docs:
// http://vincit.github.io/objection.js/
'use strict';
const db = require('../db');
const Model = require('objection').Model;
const UserPermission = require('./user_permission');

@ -1,8 +1,6 @@
// Objection Docs:
// http://vincit.github.io/objection.js/
'use strict';
const db = require('../db');
const Model = require('objection').Model;

@ -1,5 +1,3 @@
'use strict';
const express = require('express');
const validator = require('../../lib/validator');
const jwtdecode = require('../../lib/express/jwt-decode');

@ -1,5 +1,3 @@
'use strict';
const express = require('express');
const pjson = require('../../../../package.json');
const error = require('../../lib/error');

@ -1,5 +1,3 @@
'use strict';
const express = require('express');
const validator = require('../../../lib/validator');
const jwtdecode = require('../../../lib/express/jwt-decode');

@ -1,5 +1,3 @@
'use strict';
const express = require('express');
const validator = require('../../../lib/validator');
const jwtdecode = require('../../../lib/express/jwt-decode');
@ -181,6 +179,34 @@ router
}
});
/**
* Renew LE 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) => {
internalCertificate.renew(res.locals.access, {
id: parseInt(req.params.certificate_id, 10)
})
.then(result => {
res.status(200)
.send(result);
})
.catch(next);
});
/**
* Validate Certs before saving
*

@ -1,5 +1,3 @@
'use strict';
const express = require('express');
const validator = require('../../../lib/validator');
const jwtdecode = require('../../../lib/express/jwt-decode');

@ -1,5 +1,3 @@
'use strict';
const express = require('express');
const validator = require('../../../lib/validator');
const jwtdecode = require('../../../lib/express/jwt-decode');

@ -1,5 +1,3 @@
'use strict';
const express = require('express');
const validator = require('../../../lib/validator');
const jwtdecode = require('../../../lib/express/jwt-decode');

@ -1,5 +1,3 @@
'use strict';
const express = require('express');
const validator = require('../../../lib/validator');
const jwtdecode = require('../../../lib/express/jwt-decode');

@ -1,5 +1,3 @@
'use strict';
const express = require('express');
const jwtdecode = require('../../lib/express/jwt-decode');
const internalReport = require('../../internal/report');

@ -1,5 +1,3 @@
'use strict';
const express = require('express');
const jwtdecode = require('../../lib/express/jwt-decode');
const internalToken = require('../../internal/token');

@ -1,5 +1,3 @@
'use strict';
const express = require('express');
const validator = require('../../lib/validator');
const jwtdecode = require('../../lib/express/jwt-decode');

@ -1,8 +1,7 @@
'use strict';
const express = require('express');
const fs = require('fs');
const PACKAGE = require('../../../package.json');
const path = require('path')
const router = express.Router({
caseSensitive: true,
@ -29,7 +28,9 @@ router.get(/(.*)/, function (req, res, next) {
version: PACKAGE.version
});
} else {
fs.readFile('dist' + req.params.page, 'utf8', function (err, data) {
var p = path.normalize('dist' + req.params.page)
if (p.startsWith('dist')) { // Allow access to ressources under 'dist' directory only.
fs.readFile(p, 'utf8', function (err, data) {
if (err) {
res.render('index', {
version: PACKAGE.version
@ -38,6 +39,11 @@ router.get(/(.*)/, function (req, res, next) {
res.contentType('text/html').end(data);
}
});
} else {
res.render('index', {
version: PACKAGE.version
});
}
}
});

@ -1,5 +1,3 @@
'use strict';
const fs = require('fs');
const NodeRSA = require('node-rsa');
const config = require('config');
@ -7,7 +5,7 @@ const logger = require('./logger').setup;
const userModel = require('./models/user');
const userPermissionModel = require('./models/user_permission');
const authModel = require('./models/auth');
const debug_mode = process.env.NODE_ENV !== 'production';
const debug_mode = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG;
module.exports = function () {
return new Promise((resolve, reject) => {

@ -41,5 +41,7 @@ server {
}
{% endif %}
# Custom
include /data/nginx/custom/server_proxy[.]conf;
}
{% endif %}

@ -25,5 +25,7 @@ server {
}
{% endif %}
# Custom
include /data/nginx/custom/server_redirect[.]conf;
}
{% endif %}

@ -7,12 +7,20 @@
server {
listen {{ incoming_port }};
proxy_pass {{ forward_ip }}:{{ 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;
proxy_pass {{ forward_ip }}:{{ forwarding_port }};
# Custom
include /data/nginx/custom/server_stream[.]conf;
include /data/nginx/custom/server_stream_udp[.]conf;
}
{% endif %}
{% endif %}

@ -1,5 +1,3 @@
'use strict';
const $ = require('jquery');
const _ = require('underscore');
const Tokens = require('./tokens');
@ -35,7 +33,7 @@ ApiError.prototype = Object.create(Error.prototype, {
* @param {Object} [options]
* @returns {Promise}
*/
function fetch (verb, path, data, options) {
function fetch(verb, path, data, options) {
options = options || {};
return new Promise(function (resolve, reject) {
@ -55,7 +53,7 @@ function fetch (verb, path, data, options) {
contentType: options.contentType || 'application/json; charset=UTF-8',
processData: options.processData || true,
crossDomain: true,
timeout: options.timeout ? options.timeout : 15000,
timeout: options.timeout ? options.timeout : 30000,
xhrFields: {
withCredentials: true
},
@ -99,7 +97,7 @@ function fetch (verb, path, data, options) {
* @param {Array} expand
* @returns {String}
*/
function makeExpansionString (expand) {
function makeExpansionString(expand) {
let items = [];
_.forEach(expand, function (exp) {
items.push(encodeURIComponent(exp));
@ -114,7 +112,7 @@ function makeExpansionString (expand) {
* @param {String} [query]
* @returns {Promise}
*/
function getAllObjects (path, expand, query) {
function getAllObjects(path, expand, query) {
let params = [];
if (typeof expand === 'object' && expand !== null && expand.length) {
@ -128,20 +126,7 @@ function getAllObjects (path, expand, query) {
return fetch('get', path + (params.length ? '?' + params.join('&') : ''));
}
/**
* @param {String} path
* @param {FormData} form_data
* @returns {Promise}
*/
function upload (path, form_data) {
console.log('UPLOAD:', path, form_data);
return fetch('post', path, form_data, {
contentType: 'multipart/form-data',
processData: false
});
}
function FileUpload (path, fd) {
function FileUpload(path, fd) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
let token = Tokens.getTopToken();
@ -214,7 +199,7 @@ module.exports = {
Users: {
/**
* @param {Integer|String} user_id
* @param {Number|String} user_id
* @param {Array} [expand]
* @returns {Promise}
*/
@ -639,6 +624,14 @@ module.exports = {
*/
validate: function (form_data) {
return FileUpload('nginx/certificates/validate', form_data);
},
/**
* @param {Number} id
* @returns {Promise}
*/
renew: function (id) {
return fetch('post', 'nginx/certificates/' + id + '/renew');
}
}
},

@ -1,5 +1,3 @@
'use strict';
const Mn = require('backbone.marionette');
const Controller = require('../../controller');
const template = require('./item.ejs');

@ -1,5 +1,3 @@
'use strict';
const Mn = require('backbone.marionette');
const ItemView = require('./item');
const template = require('./main.ejs');

@ -1,5 +1,3 @@
'use strict';
const Mn = require('backbone.marionette');
const App = require('../main');
const AuditLogModel = require('../../models/audit-log');

@ -1,5 +1,3 @@
'use strict';
const Mn = require('backbone.marionette');
const template = require('./meta.ejs');

@ -1,5 +1,3 @@
'use strict';
const UserModel = require('../models/user');
let cache = {

@ -1,5 +1,3 @@
'use strict';
const Backbone = require('backbone');
const Cache = require('./cache');
const Tokens = require('./tokens');
@ -342,6 +340,19 @@ module.exports = {
}
},
/**
* Certificate Renew
*
* @param model
*/
showNginxCertificateRenew: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
require(['./main', './nginx/certificates/renew'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Certificate Delete Confirm
*

@ -1,5 +1,3 @@
'use strict';
const Mn = require('backbone.marionette');
const Cache = require('../cache');
const Controller = require('../controller');

@ -1,5 +1,3 @@
'use strict';
const Mn = require('backbone.marionette');
const template = require('./main.ejs');

@ -1,5 +1,3 @@
'use strict';
const Mn = require('backbone.marionette');
const template = require('./main.ejs');

@ -1,5 +1,3 @@
'use strict';
const Mn = require('backbone.marionette');
const template = require('./main.ejs');

@ -1,5 +1,3 @@
'use strict';
const Cache = ('./cache');
const messages = require('../i18n/messages.json');

@ -1,5 +1,3 @@
'use strict';
const _ = require('underscore');
const Backbone = require('backbone');
const Mn = require('../lib/marionette');

@ -1,5 +1,3 @@
'use strict';
const Mn = require('backbone.marionette');
const App = require('../../main');
const template = require('./delete.ejs');

@ -1,5 +1,3 @@
'use strict';
const Mn = require('backbone.marionette');
const App = require('../../main');
const AccessListModel = require('../../../models/access-list');

@ -1,5 +1,3 @@
'use strict';
const Mn = require('backbone.marionette');
const template = require('./item.ejs');

@ -1,5 +1,3 @@
'use strict';
const Mn = require('backbone.marionette');
const App = require('../../../main');
const template = require('./item.ejs');

@ -1,5 +1,3 @@
'use strict';
const Mn = require('backbone.marionette');
const App = require('../../../main');
const ItemView = require('./item');

@ -1,5 +1,3 @@
'use strict';
const Mn = require('backbone.marionette');
const App = require('../../main');
const AccessListModel = require('../../../models/access-list');

@ -1,5 +1,3 @@
'use strict';
const Mn = require('backbone.marionette');
const App = require('../../main');
const template = require('./delete.ejs');
@ -16,7 +14,6 @@ module.exports = Mn.View.extend({
},
events: {
'click @ui.save': function (e) {
e.preventDefault();

@ -1,5 +1,3 @@
'use strict';
const _ = require('underscore');
const Mn = require('backbone.marionette');
const App = require('../../main');

@ -4,17 +4,24 @@
</div>
</td>
<td>
<div>
<% if (provider === 'letsencrypt') { %>
<% domain_names.map(function(host) {
<div class="wrap">
<%
if (provider === 'letsencrypt') {
domain_names.map(function(host) {
if (host.indexOf('*') === -1) {
%>
<span class="tag host-link hover-pink" rel="https://<%- host %>"><%- host %></span>
<%
} else {
%>
<span class="tag"><%- host %></span>
<%
}
});
} else {
%><%- nice_name %><%
}
%>
<% } else { %>
<%- nice_name %>
<% } %>
</div>
<div class="small text-muted">
<%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %>
@ -31,6 +38,10 @@
<div class="item-action dropdown">
<a href="#" data-toggle="dropdown" class="icon"><i class="fe fe-more-vertical"></i></a>
<div class="dropdown-menu dropdown-menu-right">
<% if (provider === 'letsencrypt') { %>
<a href="#" class="renew dropdown-item"><i class="dropdown-icon fe fe-refresh-cw"></i> <%- i18n('certificates', 'force-renew') %></a>
<div class="dropdown-divider"></div>
<% } %>
<a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More