diff --git a/src/backend/src/CoreModule.js b/src/backend/src/CoreModule.js index 8110acf6..f7845243 100644 --- a/src/backend/src/CoreModule.js +++ b/src/backend/src/CoreModule.js @@ -314,6 +314,9 @@ const install = async ({ services, app, useapi }) => { const { BootScriptService } = require('./services/BootScriptService'); services.registerService('boot-script', BootScriptService); + + const { FeatureFlagService } = require('./services/FeatureFlagService'); + services.registerService('feature-flag', FeatureFlagService); } const install_legacy = async ({ services }) => { diff --git a/src/backend/src/data/hardcoded-permissions.js b/src/backend/src/data/hardcoded-permissions.js index f68c41fc..b785cea2 100644 --- a/src/backend/src/data/hardcoded-permissions.js +++ b/src/backend/src/data/hardcoded-permissions.js @@ -74,6 +74,7 @@ const hardcoded_user_group_permissions = { system: { 'ca342a5e-b13d-4dee-9048-58b11a57cc55': { 'service': {}, + 'feature': {}, }, 'b7220104-7905-4985-b996-649fdcdb3c8f': { 'service:hello-world:ii:hello-world': policy_perm('temp.es'), diff --git a/src/backend/src/middleware/featureflag.js b/src/backend/src/middleware/featureflag.js new file mode 100644 index 00000000..120cc29b --- /dev/null +++ b/src/backend/src/middleware/featureflag.js @@ -0,0 +1,22 @@ +const APIError = require("../api/APIError"); +const { Context } = require("../util/context"); + +const featureflag = options => async (req, res, next) => { + const { feature } = options; + + const context = Context.get(); + const services = context.get('services'); + const svc_featureFlag = services.get('feature-flag'); + + if ( ! await svc_featureFlag.check({ + actor: req.actor, + }, feature) ) { + const e = APIError.create('forbidden'); + e.write(res); + return; + } + + next(); +}; + +module.exports = featureflag; diff --git a/src/backend/src/services/FeatureFlagService.js b/src/backend/src/services/FeatureFlagService.js new file mode 100644 index 00000000..bbce5818 --- /dev/null +++ b/src/backend/src/services/FeatureFlagService.js @@ -0,0 +1,42 @@ +const { Context } = require("../util/context"); +const { whatis } = require("../util/langutil"); +const { PermissionUtil } = require("./auth/PermissionService"); +const BaseService = require("./BaseService"); + +/** + * FeatureFlagService is a way to let the client (frontend) know what features + * are enabled or disabled for the current user. + */ +class FeatureFlagService extends BaseService { + async check (...a) { + // allows binding call with multiple options objects; + // the last argument is the permission to check + const { options, value: permission } = (() => { + let value; + const options = {}; + for ( const arg of a ) { + if ( whatis(arg) === 'object' ) { + Object.assign(options, arg); + continue; + } + value = arg; + break; + } + return { options, value }; + })(); + + + + const actor = options.actor ?? Context.get('actor'); + + const svc_permission = this.services.get('permission'); + const reading = await svc_permission.scan(actor, `feature:${permission}`); + const l = PermissionUtil.reading_to_options(reading); + if ( l.length === 0 ) return false; + return true; + } +} + +module.exports = { + FeatureFlagService +}; diff --git a/src/backend/src/services/ShareService.js b/src/backend/src/services/ShareService.js index a2973938..b6409343 100644 --- a/src/backend/src/services/ShareService.js +++ b/src/backend/src/services/ShareService.js @@ -19,6 +19,7 @@ const APIError = require("../api/APIError"); const { get_user } = require("../helpers"); const configurable_auth = require("../middleware/configurable_auth"); +const featureflag = require("../middleware/featureflag.js"); const { Context } = require("../util/context"); const { Endpoint } = require("../util/expressutil"); const { whatis } = require("../util/langutil"); @@ -246,7 +247,10 @@ class ShareService extends BaseService { Endpoint({ route: '/', methods: ['POST'], - mw: [configurable_auth()], + mw: [ + configurable_auth(), + featureflag({ feature: 'share' }), + ], handler: async (req, res) => { const actor = Actor.adapt(req.user); return await share_sequence.call(this, {