diff --git a/layouts/errors/500.ejs b/layouts/errors/500.ejs index 1716a957..91dbb175 100644 --- a/layouts/errors/500.ejs +++ b/layouts/errors/500.ejs @@ -8,6 +8,9 @@

<%- template('error.500.heading') %>

<%- template('error.500.message') %>

+ <% if (locals.isDev && locals.err && locals.err.message) { %> +

Error: <%- locals.err.message %>

+ <% } %>
<%- include('partials/search', {style: 'homepage'}) %> diff --git a/server/errors/index.js b/server/errors/index.js new file mode 100644 index 00000000..da338158 --- /dev/null +++ b/server/errors/index.js @@ -0,0 +1,22 @@ +const messages = require('./messages') + +// For now, this should just throw for things that would stop the app from booting. + +module.exports = () => { + const { errorMessages } = messages + const { APPROVED_DOMAINS, DRIVE_TYPE, DRIVE_ID } = process.env + const errors = [] + + if (!APPROVED_DOMAINS) errors.push(errorMessages.noApprovedDomains) + if (!DRIVE_TYPE) errors.push(errorMessages.noDriveType) + if (!DRIVE_ID) errors.push(errorMessages.noDriveID) + + if (errors.length) { + console.log('***********************************************') + console.log('Your library instance has configuration issues:') + errors.forEach((message) => console.error(` > ${message}`)) + console.log('Address these issues and restart the app.') + console.log('***********************************************') + process.exit(1) + } +} diff --git a/server/errors/messages.json b/server/errors/messages.json new file mode 100644 index 00000000..b639249c --- /dev/null +++ b/server/errors/messages.json @@ -0,0 +1,8 @@ +{ + "errorMessages": { + "noApprovedDomains": "You must set the APPROVED_DOMAINS environment variable to a list of domains or a regular expression.", + "noDriveType": "No DRIVE_TYPE env variable set. Please set it to 'team' or 'folder.'", + "noDriveID": "No DRIVE_ID env variable set. Set this environment variable to the ID of your drive or folder and restart the app.", + "noFilesFound": "No files found. Ensure your DRIVE_ID and DRIVE_TYPE environment variables are set correctly and that your service account's email is shared with your drive or folder.\n\nIf you just added the account, wait a minute and try again." + } +} diff --git a/server/index.js b/server/index.js index 1fac976a..372ab253 100644 --- a/server/index.js +++ b/server/index.js @@ -7,6 +7,7 @@ const csp = require('helmet-csp') const {middleware: cache} = require('./cache') const {getMeta} = require('./list') const {allMiddleware, requireWithFallback} = require('./utils') +const checkErrors = require('./errors') const userInfo = require('./routes/userInfo') const pages = require('./routes/pages') const categories = require('./routes/categories') @@ -15,6 +16,8 @@ const readingHistory = require('./routes/readingHistory') const redirects = require('./routes/redirects') const errorPages = require('./routes/errors') +checkErrors() + const userAuth = requireWithFallback('userAuth') const customCsp = requireWithFallback('csp') diff --git a/server/routes/errors.js b/server/routes/errors.js index 5e85a9de..1aac0e58 100644 --- a/server/routes/errors.js +++ b/server/routes/errors.js @@ -63,12 +63,14 @@ module.exports = async (err, req, res, next) => { const code = messages[err.message] || 500 log.error(`Serving an error page for ${req.url}`, err) const inlined = await loadInlineAssets() + res.status(code) res.format({ html: () => { res.render(`errors/${code}`, { inlineCSS: inlined.css, + isDev: process.env.NODE_ENV === 'development', err, template: inlined.stringTemplate }) diff --git a/server/routes/pages.js b/server/routes/pages.js index 9acd77f2..6bd6b556 100644 --- a/server/routes/pages.js +++ b/server/routes/pages.js @@ -2,6 +2,9 @@ const search = require('../search') +const {getAuth} = require('../auth') +const {errorMessages} = require('../errors/messages.json') + const router = require('express-promise-router')() const {getTree, getFilenames, getMeta, getTagged} = require('../list') @@ -62,6 +65,12 @@ async function handlePage(req, res) { if (page === 'categories' || page === 'index') { const tree = await getTree() + if (!tree.children) { + // pull the auth client email to make debugging easier: + const authClient = await getAuth() + const errMsg = errorMessages.noFilesFound.replace('email', `email (${authClient.email})`) + throw new Error(errMsg) + } const categories = buildDisplayCategories(tree) res.format({ html: () => { diff --git a/server/userAuth.js b/server/userAuth.js index cacd5a91..fcca4593 100644 --- a/server/userAuth.js +++ b/server/userAuth.js @@ -12,6 +12,8 @@ const {stringTemplate: template} = require('./utils') const router = require('express-promise-router')() const domains = new Set(process.env.APPROVED_DOMAINS.split(/,\s?/g)) +const isDev = process.env.NODE_ENV === 'development' + const authStrategies = ['google', 'Slack'] let authStrategy = process.env.OAUTH_STRATEGY @@ -38,8 +40,9 @@ if (isSlackOauth) { } else { // default to google auth passport.use(new GoogleStrategy.Strategy({ - clientID: process.env.GOOGLE_CLIENT_ID, - clientSecret: process.env.GOOGLE_CLIENT_SECRET, + // some value must be passed to passport, but in dev this value does not matter + clientID: isDev ? ' ' : process.env.GOOGLE_CLIENT_ID, + clientSecret: isDev ? ' ' : process.env.GOOGLE_CLIENT_SECRET, callbackURL, userProfileURL: 'https://www.googleapis.com/oauth2/v3/userinfo', passReqToCallback: true @@ -82,7 +85,6 @@ router.get('/auth/redirect', passport.authenticate(authStrategy, {failureRedirec }) router.use((req, res, next) => { - const isDev = process.env.NODE_ENV === 'development' const passportUser = (req.session.passport || {}).user || {} if (isDev || (req.isAuthenticated() && isAuthorized(passportUser))) { setUserInfo(req) @@ -111,7 +113,7 @@ function isAuthorized(user) { } function setUserInfo(req) { - if (process.env.NODE_ENV === 'development') { + if (isDev) { // userInfo shim for development req.userInfo = { email: process.env.TEST_EMAIL || template('footer.defaultEmail'), userId: '10',