Readlebee/server/index.js

127 lines
5.2 KiB
JavaScript

'use strict'
require('make-promises-safe'); // installs an 'unhandledRejection' handler
const path = require('path');
let siteConfig;
try {
siteConfig = require('./config.json');
} catch (ex) {
console.error('Please copy `config.example.json` to `config.json` and fill it with your server\'s data.');
process.exit(1);
}
const fastify = require('fastify')({
logger: process.env.NODE_ENV !== 'production',
});
fastify.decorate('siteConfig', siteConfig); // Insert siteConfig into global fastify instance
fastify.register(require('fastify-helmet'), { // Add security stuff
contentSecurityPolicy: { // Modify Content Security Policy headers to allow content from specific domains
directives: {
'default-src': ["'self'"], // Default value
'base-uri': ["'self'"], // Default value
'block-all-mixed-content': [], // Default value
'frame-ancestors': ["'self'"], // Default value
'style-src': ["'self'", "https: 'unsafe-inline'"], // Default value
'upgrade-insecure-requests': [], // Default value
'object-src': ["'none'"], // Default value
'script-src': ["'self'", 'polyfill.io', "https: 'unsafe-inline'"], // Allow loading scripts inline (required for Choo) and from polyfill.io
'img-src': ["'self'", siteConfig.inventaireDomain, 'openlibrary.org', 'covers.openlibrary.org', "data:"], // Allow images from Inventaire, Open Library, and raw `data:` hashes
}
}
});
fastify.register(require('fastify-compress')); // Compress output data for smaller packet delivery
fastify.register(require('fastify-static'), { // Enable delivering static content efficiently
root: path.resolve(__dirname, '../public'), // all static content will be delivered from the public/ folder
});
fastify.register(require('fastify-cookie')); // Enable reading and setting http-level cookies for the sole purpose of storing login tokens
fastify.register(require('fastify-jwt'), { // Enable creating, parsing, and verifying JSON Web Tokens from the global fastify object
secret: fastify.siteConfig.jwtSecretKey, // The secret key used to generate JWTs. Make it big and random!
});
const sequelizeConfig = {
instance: 'sequelize',
autoConnect: true,
dialect: fastify.siteConfig.db_engine,
};
switch (fastify.siteConfig.db_engine) {
case 'sqlite': {
sequelizeConfig.storage = typeof fastify.siteConfig.sqlite_location !== 'undefined'
? path.resolve(__dirname, fastify.siteConfig.sqlite_location)
: path.resolve(__dirname, './database.sqlite');
break;
}
default: {
sequelizeConfig.host = fastify.siteConfig.db_host;
sequelizeConfig.port = fastify.siteConfig.db_port;
sequelizeConfig.database = fastify.siteConfig.db_database;
sequelizeConfig.username = fastify.siteConfig.db_username;
sequelizeConfig.password = fastify.siteConfig.db_password;
}
}
fastify.register(require('./fastify-plugins/fastify-sequelize'), {
config: sequelizeConfig,
registerModels: require('./sequelize/models'),
});
if (!fastify.siteConfig.email_host || !fastify.siteConfig.email_username) {
console.warn('###\nNo email server set up. You will not be able to send emails without entering your email configuration.\n###');
fastify.decorate('canEmail', false);
} else {
fastify.register(require('./fastify-plugins/fastify-nodemailer'), {
pool: true,
host: fastify.siteConfig.email_host,
port: fastify.siteConfig.email_port,
secure: true, // use TLS
auth: {
user: fastify.siteConfig.email_username,
pass: fastify.siteConfig.email_password,
},
});
}
// Every request, check to see if a valid token exists
fastify.addHook('onRequest', async (request, reply) => {
request.isLoggedInUser = false;
if (typeof request.cookies.token !== 'undefined' && fastify.jwt.verify(request.cookies.token)) {
const { id } = fastify.jwt.verify(request.cookies.token);
const user = await fastify.models.User.findByPk(id).catch(ex => fastify.log(ex));
if (!user) {
console.log('Invalid user id from token');
request.clearCookie('token', token, {
path: '/',
expires: new Date(Date.now() - 9999),
maxAge: new Date(Date.now() - 9999), // Both are set as a "just in case"
httpOnly: true, // Prevents JavaScript on the front end from grabbing it
sameSite: true, // Prevents the cookie from being used outside of this site
});
} else {
request.isLoggedInUser = true;
request.user = user;
}
}
request.language = typeof request.cookies.lang !== 'undefined' ? request.cookies.lang : 'en';
// Opt out of Google Chrome tracking everything you do.
// For more info, see: https://plausible.io/blog/google-floc
reply.header('Permissions-Policy', 'interest-cohort=()');
});
// Store i18n files in fastify object and register locales routes
fastify.register(require('./i18n'));
// Routes
fastify.register(require('./routes/public'));
fastify.register(require('./routes/books'));
fastify.register(require('./routes/account'));
fastify.register(require('./routes/shelf'));
fastify.register(require('./routes/search'));
// Start the server
fastify.listen(fastify.siteConfig.port, function (err, address) {
if (err) {
fastify.log.error(err);
process.exit(1);
}
});