Default Site customisation and new Settings space (#91)

This commit is contained in:
jc21
2019-03-04 21:19:36 +10:00
committed by GitHub
parent 6f1d38a0e2
commit 133d66c2fe
31 changed files with 893 additions and 15 deletions

View File

@@ -17,9 +17,9 @@ const internalNginx = {
* - IF BAD: update the meta with offline status and remove the config entirely
* - then reload nginx
*
* @param {Object} model
* @param {String} host_type
* @param {Object} host
* @param {Object|String} model
* @param {String} host_type
* @param {Object} host
* @returns {Promise}
*/
configure: (model, host_type, host) => {
@@ -122,6 +122,11 @@ const internalNginx = {
*/
getConfigName: (host_type, host_id) => {
host_type = host_type.replace(new RegExp('-', 'g'), '_');
if (host_type === 'default') {
return '/data/nginx/default_host/site.conf';
}
return '/data/nginx/' + host_type + '/' + host_id + '.conf';
},
@@ -153,9 +158,11 @@ const internalNginx = {
}
// Manipulate the data a bit before sending it to the template
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_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);
}
}
renderEngine
@@ -260,7 +267,7 @@ const internalNginx = {
/**
* @param {String} host_type
* @param {Object} host
* @param {Object} [host]
* @param {Boolean} [throw_errors]
* @returns {Promise}
*/
@@ -269,7 +276,7 @@ const internalNginx = {
return new Promise((resolve, reject) => {
try {
let config_file = internalNginx.getConfigName(host_type, host.id);
let config_file = internalNginx.getConfigName(host_type, typeof host === 'undefined' ? 0 : host.id);
if (debug_mode) {
logger.warn('Deleting nginx config: ' + config_file);

View File

@@ -108,7 +108,7 @@ const internalProxyHost = {
*/
update: (access, data) => {
let create_certificate = data.certificate_id === 'new';
console.log('PH UPDATE:', data);
if (create_certificate) {
delete data.certificate_id;
}

View File

@@ -0,0 +1,133 @@
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/nginx/default_www/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;

View File

@@ -0,0 +1,7 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
}
]
}

View File

@@ -0,0 +1,7 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
}
]
}

View File

@@ -0,0 +1,7 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
}
]
}

View File

@@ -0,0 +1,54 @@
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');
// TODO: add settings
let settingModel = require('../models/setting');
return 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('[' + migrate_name + '] Default settings added');
});
};
/**
* 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);
};

View File

@@ -0,0 +1,30 @@
// 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;

View File

@@ -31,6 +31,7 @@ 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'));

View File

@@ -0,0 +1,96 @@
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');
let 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;

View File

@@ -9,6 +9,13 @@
"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

View File

@@ -0,0 +1,99 @@
{
"$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"
}
}
}

View File

@@ -34,6 +34,9 @@
},
"access-lists": {
"$ref": "endpoints/access-lists.json"
},
"settings": {
"$ref": "endpoints/settings.json"
}
}
}

View File

@@ -0,0 +1,32 @@
# ------------------------------------------------------------
# Default Site
# ------------------------------------------------------------
{% if value == "congratulations" %}
# Skipping output, congratulations page configration is baked in.
{%- else %}
server {
listen 80 default;
server_name default-host.localhost;
access_log /data/logs/default_host.log combined;
{% include "_exploits.conf" %}
{%- if value == "404" %}
location / {
return 404;
}
{% endif %}
{%- if value == "redirect" %}
location / {
return 301 {{ meta.redirect }};
}
{%- endif %}
{%- if value == "html" %}
root /data/nginx/default_www;
location / {
try_files $uri /index.html ={{ meta.http_code }};
}
{%- endif %}
}
{% endif %}