Skip to content
Snippets Groups Projects
Verified Commit 45709bd3 authored by Louis's avatar Louis :fire:
Browse files

Add 'safe mode' to disable mutative requests, support SSL for DATABASE_URL based connections

parent 611c854e
No related branches found
No related tags found
No related merge requests found
......@@ -11,6 +11,7 @@ module.exports = {
web: env('WEB_URL', 'http://example.local'),
},
dev: env('NODE_ENV', 'development') === 'development',
safe_mode: env('DISABLE_MUTATION', 'false') === 'true',
security: {
use_ephemeral: env('USE_EPHEMERAL_KEYS', 'true') === 'true',
public_key: null,
......
......@@ -8,6 +8,7 @@ if (useSSL) {
}
const url = env('DATABASE_URL')
if (url) {
const { URL } = require('url')
const connectionUrl = new URL(url)
......@@ -17,6 +18,16 @@ if (url) {
username: connectionUrl.username,
password: connectionUrl.password,
port: connectionUrl.port,
ssl: useSSL,
dialectOptions: useSSL ? {
ssl: {
rejectUnauthorized: false,
ca: Buffer.from(
env('DATABASE_CA_CERT', null) ?? '',
'base64',
).toString(),
},
} : {},
log_queries: env('LOG_SQL_QUERIES', 'true') === 'true',
}
} else {
......
const HttpError = require('./HttpError')
module.exports = class SafeModeError extends HttpError {
constructor() {
super(503, `This resource is operating in "safe mode", and is exclusively accepting read-only requests`)
}
}
const HttpError = require('core/errors/HttpError')
const SafeModeError = require('core/errors/SafeModeError')
const SentryReporter = require('./SentryReporter')
module.exports = async (ctx, next) => {
......@@ -7,7 +8,11 @@ module.exports = async (ctx, next) => {
try {
await next(ctx)
} catch (e) {
await SentryReporter.report(e, ctx)
if (e instanceof SafeModeError) {
console.error(e)
} else {
await SentryReporter.report(e, ctx)
}
hasHandledError = true
if (e instanceof HttpError) {
......
const SafeModeError = require('core/errors/SafeModeError')
module.exports = async (ctx, next) => {
const { config } = require('bootstrap')
const safeMode = config('app.safe_mode')
if (safeMode) {
throw new SafeModeError()
}
return await next()
}
......@@ -5,6 +5,7 @@ const router = new Router({ prefix: '/v2' })
const controller = (path, handler) => require(`../controllers/${path}`)[handler]
const param = name => require(`../params/${name}`)
const { env, config } = require('bootstrap')
const safemode = require('http/middleware/SafeModeBlock')
apiMiddlewareGroup.forEach(middleware => router.use(middleware))
......@@ -25,27 +26,28 @@ router.get(
}),
)
router.post('/auth/login', controller('api/auth', 'login'))
router.post('/auth/register', controller('api/auth', 'register'))
router.post('/auth/login', safemode, controller('api/auth', 'login'))
router.post('/auth/register', safemode, controller('api/auth', 'register'))
router.post(
'/auth/password-reset',
safemode,
controller('api/auth', 'triggerPasswordReset'),
)
router.get('/self', controller('api/user', 'self'))
router.get('/self/surveys', controller('api/v2/surveys', 'joined'))
router.get('/self/bundles', controller('api/app', 'getBundles'))
router.put('/self/:property', controller('api/user', 'updateOne'))
router.put('/self/:property', safemode, controller('api/user', 'updateOne'))
router.get('/metrics', controller('api/content', 'getWithin'))
router.post('/metrics', controller('api/content', 'postMetric'))
router.post('/metrics', safemode, controller('api/content', 'postMetric'))
router.get('/images', noop)
router.post('/images', noop)
router.post('/images/:imageId/share', noop)
router.get('/uploads', noop)
router.post('/uploads', controller('api/v2/uploads', 'createUpload'))
router.post('/uploads', safemode, controller('api/v2/uploads', 'createUpload'))
router.get('/uploads/:upload_id', noop)
router.delete('/uploads/:upload_id', noop)
router.put('/uploads/:upload_id/:property', noop)
......@@ -57,12 +59,12 @@ router.get('/surveys', controller('api/v2/surveys', 'list'))
router.get('/excerpts', controller('api/v2/surveys', 'listExcerpts'))
router.get('/excerpts/:excerpt', controller('api/v2/surveys', 'getExcerpt'))
router.get('/surveys/:survey', controller('api/v2/surveys', 'get'))
router.post('/surveys/:survey/membership', controller('api/v2/surveys', 'join'))
router.delete('/surveys/:survey/membership', controller('api/v2/surveys', 'leave'))
router.post('/surveys/:survey/membership', safemode, controller('api/v2/surveys', 'join'))
router.delete('/surveys/:survey/membership', safemode, controller('api/v2/surveys', 'leave'))
if (config('app.dev')) {
router.post('/surveys/factory', controller('api/v2/factories', 'survey'))
router.post('/surveys/factory', safemode, controller('api/v2/factories', 'survey'))
}
router.post('/an/ev', controller('api/analytics', 'track'))
router.post('/an/ev', safemode, controller('api/analytics', 'track'))
module.exports = router
......@@ -17,6 +17,7 @@ const loaders = require('http/middleware/MountLoaders')
const userGate = require('http/middleware/RequiresAuth')
const authRedirect = require('http/middleware/RedirectToLogin')
const device = require('http/middleware/DeviceProperties').extractDevice
const safemode = require('http/middleware/SafeModeBlock')
const createOIDCServer = require('domain/auth/oidc/OIDCServer')
......@@ -103,61 +104,67 @@ function mount(api) {
}
})
api.post('/metrics', controller('api/content', 'postMetric'))
api.post('/metrics', safemode, controller('api/content', 'postMetric'))
api.get('/metrics', controller('api/content', 'getWithin'))
api.get('/images', controller('api/storage', 'getFiles'))
api.post(
'/images',
safemode,
upload.single('featured_image'),
controller('api/storage', 'saveFile'),
)
api.post(
'/images/:imageId/feature',
safemode,
controller('api/storage', 'featureImage'),
)
/** @deprecated */
api.post(
'/feature',
safemode,
upload.single('featured_image'),
controller('api/storage', 'saveFile'),
)
api.get('/feed', controller('api/storage', 'feed'))
api.post('/feed/:fileId/like', controller('api/storage', 'like'))
api.post('/feed/:fileId/unlike', controller('api/storage', 'unlike'))
api.post('/feed/:fileId/like', safemode, controller('api/storage', 'like'))
api.post('/feed/:fileId/unlike', safemode, controller('api/storage', 'unlike'))
api.post('/register', controller('api/auth', 'register'))
api.post('/register', safemode, controller('api/auth', 'register'))
api.post('/login', controller('api/auth', 'login'))
api.post('/auth/reset-token', controller('api/auth', 'triggerPasswordReset'))
api.post('/auth/reset-token', safemode, controller('api/auth', 'triggerPasswordReset'))
api.post(
'/auth/reset-password',
safemode,
controller('api/auth', 'handlePasswordReset'),
)
api.param('oauthClientId', param('oauth_client'))
api.get('/oauth/clients', controller('api/oauth', 'listClients'))
api.post('/oauth/clients', controller('api/oauth', 'createClient'))
api.post('/oauth/clients', safemode, controller('api/oauth', 'createClient'))
api.post(
'/oauth/clients/:oauthClientId/redirects',
safemode,
controller('api/oauth', 'addClientRedirect'),
)
api.delete(
'/oauth/clients/:oauthClientId/redirects',
safemode,
controller('api/oauth', 'removeClientRedirect'),
)
api.get('/self', controller('api/user', 'self'))
api.get('/self/bundles', controller('api/app', 'getBundles'))
api.put('/self/:property', controller('api/user', 'updateOne'))
api.put('/self/:property', safemode, controller('api/user', 'updateOne'))
api.post('/an/id', async ctx => {})
api.post('/an/ev', controller('api/analytics', 'track'))
api.post('/an/ev', safemode, controller('api/analytics', 'track'))
api.post('/feedback', controller('api/feedback', 'send'))
api.post('/feedback', safemode, controller('api/feedback', 'send'))
api.use(v2.allowedMethods())
api.use(v2.routes())
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment