Initial commit for keyServer in Node.JS

master
Felix Stupp 6 years ago
commit aa7019c1ac

165
.gitignore vendored

@ -0,0 +1,165 @@
# Created by https://www.gitignore.io/api/node,webstorm,visualstudiocode
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
### WebStorm ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
### WebStorm Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr
# Sonarlint plugin
.idea/sonarlint
# End of https://www.gitignore.io/api/node,webstorm,visualstudiocode
### Public Directory ###
/public/assets/stylesheets/*.css
/cache/
/logs/

@ -0,0 +1,28 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<DBN-PSQL>
<case-options enabled="false">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false" />
</DBN-PSQL>
<DBN-SQL>
<case-options enabled="false">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false">
<option name="STATEMENT_SPACING" value="one_line" />
<option name="CLAUSE_CHOP_DOWN" value="chop_down_if_statement_long" />
<option name="ITERATION_ELEMENTS_WRAPPING" value="chop_down_if_not_single" />
</formatting-settings>
</DBN-SQL>
</code_scheme>
</component>

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

@ -0,0 +1,32 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="CssUnknownProperty" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myCustomPropertiesEnabled" value="false" />
<option name="myIgnoreVendorSpecificProperties" value="true" />
<option name="myCustomPropertiesList">
<value>
<list size="0" />
</value>
</option>
</inspection_tool>
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myValues">
<value>
<list size="8">
<item index="0" class="java.lang.String" itemvalue="nobr" />
<item index="1" class="java.lang.String" itemvalue="noembed" />
<item index="2" class="java.lang.String" itemvalue="comment" />
<item index="3" class="java.lang.String" itemvalue="noscript" />
<item index="4" class="java.lang.String" itemvalue="embed" />
<item index="5" class="java.lang.String" itemvalue="script" />
<item index="6" class="java.lang.String" itemvalue="marquee" />
<item index="7" class="java.lang.String" itemvalue="blink" />
</list>
</value>
</option>
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
<inspection_tool class="HtmlUnknownTarget" enabled="false" level="WARNING" enabled_by_default="false" />
</profile>
</component>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/keyServer.iml" filepath="$PROJECT_DIR$/.idea/keyServer.iml" />
</modules>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectTasksOptions" suppressed-tasks="Less" />
</project>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="WebResourcesPaths">
<contentEntries>
<entry url="file://$PROJECT_DIR$">
<entryData>
<resourceRoots>
<path value="file://$PROJECT_DIR$/public" />
</resourceRoots>
</entryData>
</entry>
</contentEntries>
</component>
</project>

@ -0,0 +1,14 @@
{
// Verwendet IntelliSense zum Ermitteln möglicher Attribute.
// Zeigen Sie auf vorhandene Attribute, um die zugehörigen Beschreibungen anzuzeigen.
// Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Local",
"program": "${workspaceFolder}/bin/www"
}
]
}

101
app.js

@ -0,0 +1,101 @@
let config = require("./libs/configuration");
let express = require('express');
let createError = require("http-errors");
let path = require('path');
let cookieParser = require('cookie-parser');
//let lessMiddleware = require('less-middleware');
let logger = require('morgan');
let expressLayout = require('express-layout');
let hbs = require('hbs');
//let expressValidator = require('express-validator');
//let expressSession = require('express-session');
//let sessionMySQL = require("connect-mysql")(expressSession);
let compression = require("compression");
//let expressMinify = require('express-minify');
//let expressMinifyHtml = require('express-minify-html');
let app = express();
// Configure Views
app.set('views', path.join(__dirname, 'views'));
app.engine('hbs', hbs.__express);
app.set('view engine', 'hbs');
app.set('layouts', './views');
app.set('layout', 'html');
app.set('trust proxy', 'loopback');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({extended: false}));
app.use(expressLayout());
//app.use(expressValidator());
/*app.use(expressSession({
//store: require("./libs/SessionStorage"),
secret: config.session.secret,
saveUninitialized: false,
resave: true,
cookie: {
secure: false,
httpOnly: true,
maxAge: config.session.cookieMaxAge,
}
}));*/
app.use(cookieParser());
app.use(compression());
/*
app.use(expressMinify({
cache: __dirname + "/cache",
jsMatch: /javascript/,
cssMatch: /css/,
jsonMatch: /json/,
lessMatch: /less/,
}));
*/
/*
app.use(expressMinifyHtml({
override: true,
exception_url: false,
htmlMinifier: {
removeComments: true,
collapseWhitespace: true,
collapseBooleanAttributes: true,
removeAttributeQuotes: true,
removeEmptyAttributes: true,
minifyJS: true,
},
}));
*/
//app.use(lessMiddleware(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'static'), {
dotfiles: "ignore",
index: "index.htm",
redirect: false
}));
/* Routes */
function route(path, route) {
app.use(path, require('./routes/' + route));
}
route("/ssh", "ssh");
// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404));
});
// error handler
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
require("prepare")(app);
module.exports = app;

@ -0,0 +1,102 @@
#!/usr/bin/env node
// Helper
Date.createFromMysql = function (mysql_string) {
let t, result = null;
if (typeof mysql_string === 'string') {
t = mysql_string.split(/[- :]/);
//when t[3], t[4] and t[5] are missing they defaults to zero
result = new Date(t[0], t[1] - 1, t[2], t[3] || 0, t[4] || 0, t[5] || 0);
}
return result;
};
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('dsapage:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
console.log("Listening on " + bind);
}

@ -0,0 +1,203 @@
let mysql = require('mysql');
let config = require("./configuration");
class Database {
constructor() {
this.pool = mysql.createPool({
connectionLimit: 50,
connectTimeout: 5000,
acquireTimeout: 5000,
host: config.sqlDatabase.host,
user: config.sqlDatabase.user,
password: config.sqlDatabase.password,
database: config.sqlDatabase.database,
multipleStatements: true,
});
/*
this.pool.on('connection', function(connection) {
});
*/
}
get q() {
return new SimpleQuery(this);
}
static instance() {
if (!Database.INSTANCE)
Database.INSTANCE = new Database();
return Database.INSTANCE;
}
con(cb) {
return this.pool.getConnection(cb);
}
query(sql, values, cb) {
this.pool.query(sql, values, (e, d) => cb(e, d));
}
}
class SelectElement {
constructor(row, asName) {
this.row = row;
this.asName = asName;
}
get name() {
return this.asName || this.row;
}
}
class SimpleQuery {
constructor(db) {
this.db = db;
this.selects = [];
this.table = null;
this.filters = [];
this.orders = [];
this.limitVal = null;
}
static compileArray(arr, first, separator, comp) {
let res = "";
if (arr.length <= 0)
return res;
for (let i = 0; i < arr.length; i++) {
if (i < 1)
res += first;
else
res += separator;
res += comp(arr[i]);
}
return res;
}
select(row, asName) {
if (Array.isArray(row)) {
if (!Array.isArray(asName))
asName = [];
for (let i = 0; i < row.length; i++)
this.select(row[i], asName[i]);
} else {
this.selects.push(new SelectElement(row, asName));
}
return this;
}
from(tab) {
this.table = tab;
return this;
}
filter(row, val) {
this.filters.push({row: row, val: val});
return this;
}
order(row, desc) {
this.orders.push({row: row, desc: !!desc});
return this;
}
limit(limit) {
this.limitVal = limit;
return this;
}
reqSel(minVal) {
if (this.selects.length < minVal)
throw "Missing selectors";
return this;
}
get(cb) {
let sel = this.selects[0].name;
this.first(function (e, d) {
if (d)
cb(e, d[sel]);
else
cb(e, d);
});
}
first(cb) {
this.limit(1).all(function (e, d) {
if (d)
cb(e, d[0]);
else
cb(e, d);
});
}
list(cb) {
let value = this.selects[0].name;
this.all(function (e, d) {
if (d)
cb(e, d.map(e => e[value]));
else
cb(e, d);
});
}
array(cb) {
let index = this.selects[0].name;
let value = this.selects[1].name;
this.reqSel(2).all(function (e, d) {
if (d) {
let res = [];
d.forEach(e => res[e[index]] = e[value]);
cb(e, res);
} else {
cb(e, d);
}
});
}
index(cb) {
let index = this.selects[0].name;
this.reqSel(1).all((e, d) => {
if (d) {
let res = [];
d.forEach(e => res[e[index]] = e);
cb(e, res);
} else {
cb(e, d);
}
});
}
all(cb) {
if (this.selects.length < 1)
this.select("*");
if (!this.table)
throw "Missing Table Name";
let values = [];
let query = SimpleQuery.compileArray(this.selects, "SELECT ", ",", function (o) {
if (o.asName) {
return o.row + " AS " + o.asName;
}
return o.row;
}) + " FROM " + this.table + SimpleQuery.compileArray(this.filters, " WHERE", " AND", function (o) {
values.push(o.val);
return " " + o.row + " = ?";
}) + SimpleQuery.compileArray(this.orders, " ORDER BY ", ",", function (o) {
if (o.desc)
return o.row + " DESC";
else
return o.row + " ASC";
});
if (this.limitVal)
query += " LIMIT " + this.limitVal;
this.db.query(query, values, cb);
}
}
module.exports = Database;

@ -0,0 +1,9 @@
module.exports = {
domain: "keys.banananet.work",
sqlDatabase: {
host: "localhost",
user: 'publicKeys',
password: 'cxekSE7Kgp4PhjoYrjhEP5DuSoKpKGfNhEbm77DZSdxrHXuFU5x9aBfQiHEVcJRyyM5Qd2pdsWp4ZxRgYxZHfCmJrrLpmcGJX5J5yVbL5CcMVSuVe5pGUWmR3638cPXyeKd5g2xzurJThEJUbYKvv7dWFHgZj3dD4qL5KUTDdiRfHiLZ9DDU3nEZhQdc6rnipdrJCT5RdzHhzFjFTqCZwbqucyycAHjUzhVotGctFP6mS6an3hQSnN3HbLCne54y',
database: 'publicKeys',
},
};

5135
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,33 @@
{
"name": "keyserver",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"async": "^2.6.1",
"bcrypt": "^3.0.0",
"compression": "^1.7.3",
"connect-mysql": "^2.1.7",
"cookie-parser": "~1.4.3",
"debug": "~2.6.9",
"express": "~4.16.0",
"express-layout": "^0.1.0",
"express-minify": "^1.0.0",
"express-minify-html": "^0.12.0",
"express-session": "^1.15.6",
"express-validator": "^5.3.0",
"handlebars": "^4.0.12",
"hbs": "~4.0.1",
"http-errors": "~1.6.2",
"ical-generator": "^1.0.4",
"less-middleware": "~3.0.1",
"markdown-it": "^8.4.2",
"moment": "^2.22.2",
"morgan": "~1.9.0",
"mysql": "^2.16.0",
"npm": "^6.4.1",
"utils-merge": "~1.0.1"
}
}

@ -0,0 +1,24 @@
let fs = require("fs");
let path = require("path");
const staticDir = path.join(__dirname, "static");
module.exports = (app) => {
function renderAndSave(file, template, options) {
file = path.join(staticDir, file);
app.render(template, options, (e, r) => {
if (e) return fs.writeFile(file, "<!DOCTYPE html><html><head><title>Something went wrong!</title></head><body><h1>Something went wrong!</h1><p>Could not render this page! Please contact the administrator!</p></body></html>");
return fs.writeFile(file, r);
});
}
if (!fs.existsSync(staticDir)) fs.mkdirSync(staticDir);
[
["index.htm", "linkList", {
links: [
{href: "/ssh", name: "All SSH Keys"},
]
}],
].forEach(e => renderAndSave(e[0], e[1], e[2]));
};

@ -0,0 +1,40 @@
const user = ":user([^@]+)";
const host = ":host([^@]+)";
const type = ":type((dsa|rsa|ecdsa|ed25519))";
let express = require("express");
let db = require("../libs/Database").instance();
let router = express.Router();
let userRouter = express.Router();
function toList(a, b) {
return a + "\n" + b;
}
router.use(["/user", "/users", "/userKeys"], userRouter);
function userReq(req, res) {
let p = req.params;
let q = db.q.select("publicKeyComment");
if (p.user) q.filter("user", p.user);
if (p.host) q.filter("host", p.host);
if (p.type) q.filter("type", p.type);
q.list((e, d) => {
if (e) return res.status(500).send("ERROR");
res.type("text/plain");
res.send(d.reduce(toList));
})
}
[
"/",
"/" + type,
"/@" + host,
"/" + user,
"/" + user + "@" + host,
"/@" + host + "/" + type,
"/" + user + "/" + type,
"/" + user + "@" + host + "/" + type,
].forEach(e => userRouter.get(e, userReq));
module.exports = router;

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
</head>
<body>
<header>
<h1>{{title}}</h1>
</header>
<main>
{{{body}}}
</main>
<footer>
<address>Key Server of <a href="https://banananet.work/">banananet.work</a></address>
</footer>
</body>
</html>

@ -0,0 +1,5 @@
<ul>
{{#each links as |link|}}
<li><a href="{{link.href}}">{{#if link.name}}{{link.name}}{{else}}{{link.href}}{{/if}}</a></li>
{{/each}}
</ul>

@ -0,0 +1 @@
{{{ body }}}
Loading…
Cancel
Save