feat(query-params): proper handling in case of invalid credentials
This commit is contained in:
committed by
Benedikt Rötsch
parent
ff305e7d34
commit
46074b508d
53
app.js
53
app.js
@@ -13,11 +13,14 @@ require('dotenv').config({ path: 'variables.env' })
|
||||
const helpers = require('./helpers')
|
||||
const { translate, initializeTranslations } = require('./i18n/i18n')
|
||||
const breadcrumb = require('./lib/breadcrumb')
|
||||
const { updateCookie } = require('./lib/cookies')
|
||||
const settings = require('./lib/settings')
|
||||
const routes = require('./routes/index')
|
||||
const { getSpace } = require('./services/contentful')
|
||||
const { catchErrors } = require('./handlers/errorHandlers')
|
||||
|
||||
const SETTINGS_NAME = 'theExampleAppSettings'
|
||||
|
||||
const app = express()
|
||||
|
||||
// View engine setup
|
||||
@@ -53,27 +56,11 @@ app.use(catchErrors(async function (request, response, next) {
|
||||
response.locals.helpers = helpers
|
||||
|
||||
// Make query string available in templates to render links properly
|
||||
const qs = querystring.stringify(request.query)
|
||||
// Creates a query string which adds the current credentials to links
|
||||
// To other implementations of this app in the about modal
|
||||
let settingsQuery = {
|
||||
editorial_features: response.locals.settings.editorialFeatures ? 'enabled' : 'disabled'
|
||||
}
|
||||
if (
|
||||
response.locals.settings.spaceId !== process.env.CONTENTFUL_SPACE_ID ||
|
||||
response.locals.settings.deliveryToken !== process.env.CONTENTFUL_DELIVERY_TOKEN ||
|
||||
response.locals.settings.previewToken !== process.env.CONTENTFUL_PREVIEW_TOKEN
|
||||
) {
|
||||
settingsQuery = Object.assign({}, settingsQuery, request.query, {
|
||||
space_id: response.locals.settings.spaceId,
|
||||
delivery_token: response.locals.settings.deliveryToken,
|
||||
preview_token: response.locals.settings.previewToken
|
||||
})
|
||||
}
|
||||
const cleanQuery = helpers.cleanupQueryParameters(request.query)
|
||||
const qs = querystring.stringify(cleanQuery)
|
||||
|
||||
const settingsQs = querystring.stringify(settingsQuery)
|
||||
response.locals.queryString = qs ? `?${qs}` : ''
|
||||
response.locals.queryStringSettings = settingsQs ? `?${settingsQs}` : ''
|
||||
response.locals.queryStringSettings = response.locals.queryString
|
||||
response.locals.query = request.query
|
||||
response.locals.currentPath = request.path
|
||||
|
||||
@@ -93,12 +80,24 @@ app.use(catchErrors(async function (request, response, next) {
|
||||
}
|
||||
]
|
||||
|
||||
// Set currently used api
|
||||
response.locals.currentApi = apis
|
||||
.find((api) => api.id === (request.query.api || 'cda'))
|
||||
|
||||
const space = await getSpace()
|
||||
response.locals.locales = space.locales
|
||||
next()
|
||||
}))
|
||||
|
||||
// Test space connection and attach space related data for views if possible
|
||||
app.use(catchErrors(async function (request, response, next) {
|
||||
// Catch misconfigured space credentials and display settings page
|
||||
try {
|
||||
const space = await getSpace()
|
||||
|
||||
// Update credentials in cookie when space connection is successful
|
||||
updateCookie(response, SETTINGS_NAME, settings)
|
||||
|
||||
// Get available locales from space
|
||||
response.locals.locales = space.locales
|
||||
const defaultLocale = response.locals.locales
|
||||
.find((locale) => locale.default)
|
||||
|
||||
@@ -110,6 +109,18 @@ app.use(catchErrors(async function (request, response, next) {
|
||||
if (!response.locals.currentLocale) {
|
||||
response.locals.currentLocale = defaultLocale
|
||||
}
|
||||
|
||||
// Creates a query string which adds the current credentials to links
|
||||
// To other implementations of this app in the about modal
|
||||
helpers.updateSettingsQuery(request, response, response.locals.settings)
|
||||
} catch (error) {
|
||||
if ([401, 404].includes(error.response.status)) {
|
||||
// If we can't connect to the space, force the settings page to be shown.
|
||||
response.locals.forceSettingsRoute = true
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
next()
|
||||
}))
|
||||
|
||||
|
||||
36
helpers.js
36
helpers.js
@@ -1,4 +1,6 @@
|
||||
const marked = require('marked')
|
||||
const querystring = require('querystring')
|
||||
|
||||
const { translate } = require('./i18n/i18n')
|
||||
|
||||
// Parse markdown text
|
||||
@@ -19,7 +21,7 @@ module.exports.formatMetaTitle = (title, localeCode = 'en-US') => {
|
||||
return `${title.charAt(0).toUpperCase()}${title.slice(1)} — ${translate('defaultTitle', localeCode)}`
|
||||
}
|
||||
|
||||
module.exports.isCustomCredentials = (settings) => {
|
||||
function isCustomCredentials (settings) {
|
||||
const spaceId = process.env.CONTENTFUL_SPACE_ID
|
||||
const deliveryToken = process.env.CONTENTFUL_DELIVERY_TOKEN
|
||||
const previewToken = process.env.CONTENTFUL_PREVIEW_TOKEN
|
||||
@@ -29,6 +31,38 @@ module.exports.isCustomCredentials = (settings) => {
|
||||
settings.previewToken !== previewToken
|
||||
}
|
||||
|
||||
function cleanupQueryParameters (query) {
|
||||
const cleanQuery = Object.assign({}, query)
|
||||
delete cleanQuery.space_id
|
||||
delete cleanQuery.delivery_token
|
||||
delete cleanQuery.preview_token
|
||||
delete cleanQuery.reset
|
||||
return cleanQuery
|
||||
}
|
||||
|
||||
function updateSettingsQuery (request, response, settings) {
|
||||
const cleanQuery = cleanupQueryParameters(request.query)
|
||||
|
||||
let settingsQuery = Object.assign({}, cleanQuery, {
|
||||
editorial_features: settings.editorialFeatures ? 'enabled' : 'disabled'
|
||||
})
|
||||
|
||||
if (isCustomCredentials(settings)) {
|
||||
settingsQuery = Object.assign(settingsQuery, {
|
||||
space_id: settings.spaceId,
|
||||
delivery_token: settings.deliveryToken,
|
||||
preview_token: settings.previewToken
|
||||
})
|
||||
}
|
||||
|
||||
const settingsQs = querystring.stringify(settingsQuery)
|
||||
response.locals.queryStringSettings = settingsQs ? `?${settingsQs}` : ''
|
||||
}
|
||||
|
||||
module.exports.isCustomCredentials = isCustomCredentials
|
||||
module.exports.cleanupQueryParameters = cleanupQueryParameters
|
||||
module.exports.updateSettingsQuery = updateSettingsQuery
|
||||
|
||||
/**
|
||||
* Evil users might try to add base64 url data to execute js code
|
||||
* so we should purge any potentially harmful data to mitigate risk
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
*/
|
||||
|
||||
const { initClients } = require('../services/contentful')
|
||||
const { updateCookie } = require('./cookies')
|
||||
|
||||
const SETTINGS_NAME = 'theExampleAppSettings'
|
||||
|
||||
module.exports = async function settingsMiddleware (request, response, next) {
|
||||
// Set default settings based on environment variables
|
||||
@@ -28,7 +25,6 @@ module.exports = async function settingsMiddleware (request, response, next) {
|
||||
deliveryToken: delivery_token,
|
||||
previewToken: preview_token
|
||||
}
|
||||
updateCookie(response, SETTINGS_NAME, settings)
|
||||
}
|
||||
|
||||
// Allow enabling and disabling of editorial features via query parameters
|
||||
@@ -37,7 +33,6 @@ module.exports = async function settingsMiddleware (request, response, next) {
|
||||
if (typeof editorial_features !== 'undefined') {
|
||||
delete request.query.editorial_features
|
||||
settings.editorialFeatures = editorial_features === 'enabled'
|
||||
updateCookie(response, SETTINGS_NAME, settings)
|
||||
}
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
|
||||
@@ -12,6 +12,15 @@ const { getSettings, postSettings } = require('./settings')
|
||||
const { getLandingPage } = require('./landingPage')
|
||||
const { getImprint } = require('./imprint')
|
||||
|
||||
// Display settings in case of invalid credentials
|
||||
router.all('*', async (request, response, next) => {
|
||||
if (response.locals.forceSettingsRoute) {
|
||||
await getSettings(request, response, next)
|
||||
return
|
||||
}
|
||||
next()
|
||||
})
|
||||
|
||||
// GET the home landing page
|
||||
router.get('/', catchErrors(getLandingPage))
|
||||
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
* This module renders the settings page when `settings` route is requested
|
||||
* it also saves the settings to a cookie
|
||||
*/
|
||||
const { uniqWith, isEqual } = require('lodash')
|
||||
const { createClient } = require('contentful')
|
||||
const { initClients, getSpace } = require('./../services/contentful')
|
||||
|
||||
const { isCustomCredentials, updateSettingsQuery } = require('../helpers')
|
||||
const { updateCookie } = require('../lib/cookies')
|
||||
const { translate } = require('../i18n/i18n')
|
||||
const { uniqWith, isEqual } = require('lodash')
|
||||
const SETTINGS_NAME = 'theExampleAppSettings'
|
||||
const { initClients, getSpace } = require('../services/contentful')
|
||||
|
||||
const querystring = require('querystring')
|
||||
const SETTINGS_NAME = 'theExampleAppSettings'
|
||||
|
||||
async function renderSettings (response, opts) {
|
||||
// Get connected space to display the space name on top of the settings
|
||||
@@ -17,9 +18,8 @@ async function renderSettings (response, opts) {
|
||||
try {
|
||||
space = await getSpace()
|
||||
} catch (error) {
|
||||
// We throw the error here, it will be handled by the error middleware
|
||||
// We keep space false to ensure the "Connected to" box is not shown.
|
||||
throw (error)
|
||||
// We handle errors within the settings page.
|
||||
// No need to throw here.
|
||||
}
|
||||
|
||||
response.render('settings', {
|
||||
@@ -42,9 +42,23 @@ async function renderSettings (response, opts) {
|
||||
* @returns {undefined}
|
||||
*/
|
||||
module.exports.getSettings = async (request, response, next) => {
|
||||
const currentLocale = response.locals.currentLocale
|
||||
const { settings } = response.locals
|
||||
|
||||
const errorList = await generateErrorList(settings, currentLocale)
|
||||
|
||||
// If no errors detected, update app to use new settings
|
||||
if (!errorList.length) {
|
||||
applyUpdatedSettings(request, response, settings)
|
||||
}
|
||||
|
||||
const errors = generateErrorDictionary(errorList)
|
||||
|
||||
await renderSettings(response, {
|
||||
settings
|
||||
settings,
|
||||
errors,
|
||||
hasErrors: errorList.length > 0,
|
||||
success: isCustomCredentials(settings) && errorList.length === 0
|
||||
})
|
||||
}
|
||||
|
||||
@@ -60,7 +74,6 @@ module.exports.getSettings = async (request, response, next) => {
|
||||
*/
|
||||
module.exports.postSettings = async (request, response, next) => {
|
||||
const currentLocale = response.locals.currentLocale
|
||||
let errorList = []
|
||||
let { spaceId, deliveryToken, previewToken, editorialFeatures } = request.body
|
||||
|
||||
if (request.query.reset) {
|
||||
@@ -76,6 +89,27 @@ module.exports.postSettings = async (request, response, next) => {
|
||||
editorialFeatures: !!editorialFeatures
|
||||
}
|
||||
|
||||
const errorList = await generateErrorList(settings, currentLocale)
|
||||
|
||||
// If no errors detected, update app to use new settings
|
||||
if (!errorList.length) {
|
||||
applyUpdatedSettings(request, response, settings)
|
||||
}
|
||||
|
||||
const errors = generateErrorDictionary(errorList)
|
||||
|
||||
await renderSettings(response, {
|
||||
settings,
|
||||
errors,
|
||||
hasErrors: errorList.length > 0,
|
||||
success: errorList.length === 0
|
||||
})
|
||||
}
|
||||
|
||||
async function generateErrorList (settings, currentLocale) {
|
||||
const { spaceId, deliveryToken, previewToken } = settings
|
||||
let errorList = []
|
||||
|
||||
// Validate required fields.
|
||||
if (!spaceId) {
|
||||
errorList.push({
|
||||
@@ -157,29 +191,16 @@ module.exports.postSettings = async (request, response, next) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errorList = uniqWith(errorList, isEqual)
|
||||
// If no errors, then cache the new settings in the cookie
|
||||
if (!errorList.length) {
|
||||
// Store new settings
|
||||
updateCookie(response, SETTINGS_NAME, settings)
|
||||
response.locals.settings = settings
|
||||
|
||||
const settingsQuery = Object.assign({}, request.query, {
|
||||
space_id: response.locals.settings.spaceId,
|
||||
delivery_token: response.locals.settings.deliveryToken,
|
||||
preview_token: response.locals.settings.previewToken,
|
||||
editorial_features: response.locals.settings.editorialFeatures ? 'enabled' : 'disabled'
|
||||
})
|
||||
|
||||
const settingsQs = querystring.stringify(settingsQuery)
|
||||
response.locals.queryStringSettings = settingsQs ? `?${settingsQs}` : ''
|
||||
// Reinit clients
|
||||
initClients(settings)
|
||||
return errorList
|
||||
}
|
||||
|
||||
// Generate error dictionary
|
||||
// Format: { FIELD_NAME: [array, of, error, messages] }
|
||||
const errors = errorList.reduce((errors, error) => {
|
||||
function generateErrorDictionary (errorList) {
|
||||
return errorList.reduce((errors, error) => {
|
||||
return {
|
||||
...errors,
|
||||
[error.field]: [
|
||||
@@ -188,10 +209,16 @@ module.exports.postSettings = async (request, response, next) => {
|
||||
]
|
||||
}
|
||||
}, {})
|
||||
await renderSettings(response, {
|
||||
settings,
|
||||
errors,
|
||||
hasErrors: errorList.length > 0,
|
||||
success: errorList.length === 0
|
||||
})
|
||||
}
|
||||
|
||||
function applyUpdatedSettings (request, response, settings) {
|
||||
// Store new settings
|
||||
updateCookie(response, SETTINGS_NAME, settings)
|
||||
response.locals.settings = settings
|
||||
|
||||
// Update query settings string
|
||||
updateSettingsQuery(request, response, settings)
|
||||
|
||||
// Reinit clients
|
||||
initClients(settings)
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ block content
|
||||
br
|
||||
button(type="submit") #{translate('resetCredentialsLabel', currentLocale.code)}
|
||||
br
|
||||
a(href=`${baseUrl}/?space_id=${settings.spaceId}&preview_token=${settings.previewToken}&delivery_token=${settings.deliveryToken}&api=${currentApi.id}${settings.editorialFeatures ? '&enable_editorial_features' : ''}` class="status-block__sharelink") #{translate('copyLinkLabel', currentLocale.code)}
|
||||
a(href=`${baseUrl}/${queryStringSettings}` class="status-block__sharelink") #{translate('copyLinkLabel', currentLocale.code)}
|
||||
p
|
||||
em #{translate('overrideConfigLabel', currentLocale.code)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user