mirror of
https://github.com/strapi/strapi.git
synced 2025-09-14 11:08:35 +00:00
Runtime server feature flag
This commit is contained in:
parent
9211620591
commit
5a2b379fa0
2
packages/core/admin/ee/server/bootstrap.js
vendored
2
packages/core/admin/ee/server/bootstrap.js
vendored
@ -17,5 +17,7 @@ module.exports = async () => {
|
||||
await actionProvider.registerMany(actions.auditLogs);
|
||||
}
|
||||
|
||||
// TODO: check admin seats
|
||||
|
||||
await executeCEBootstrap();
|
||||
};
|
||||
|
@ -1,80 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
sso: [
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/providers',
|
||||
handler: 'authentication.getProviders',
|
||||
config: { auth: false },
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/connect/:provider',
|
||||
handler: 'authentication.providerLogin',
|
||||
config: { auth: false },
|
||||
},
|
||||
{
|
||||
method: 'POST',
|
||||
path: '/connect/:provider',
|
||||
handler: 'authentication.providerLogin',
|
||||
config: { auth: false },
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/providers/options',
|
||||
handler: 'authentication.getProviderLoginOptions',
|
||||
config: {
|
||||
policies: [
|
||||
'admin::isAuthenticatedAdmin',
|
||||
{ name: 'admin::hasPermissions', config: { actions: ['admin::provider-login.read'] } },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
method: 'PUT',
|
||||
path: '/providers/options',
|
||||
handler: 'authentication.updateProviderLoginOptions',
|
||||
config: {
|
||||
policies: [
|
||||
'admin::isAuthenticatedAdmin',
|
||||
{ name: 'admin::hasPermissions', config: { actions: ['admin::provider-login.update'] } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
'audit-logs': [
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/audit-logs',
|
||||
handler: 'auditLogs.findMany',
|
||||
config: {
|
||||
policies: [
|
||||
'admin::isAuthenticatedAdmin',
|
||||
{
|
||||
name: 'admin::hasPermissions',
|
||||
config: {
|
||||
actions: ['admin::audit-logs.read'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/audit-logs/:id',
|
||||
handler: 'auditLogs.findOne',
|
||||
config: {
|
||||
policies: [
|
||||
'admin::isAuthenticatedAdmin',
|
||||
{
|
||||
name: 'admin::hasPermissions',
|
||||
config: {
|
||||
actions: ['admin::audit-logs.read'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
@ -1,17 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
// eslint-disable-next-line node/no-extraneous-require
|
||||
const { features } = require('@strapi/strapi/lib/utils/ee');
|
||||
const featuresRoutes = require('./features-routes');
|
||||
|
||||
const getFeaturesRoutes = () => {
|
||||
return Object.entries(featuresRoutes).flatMap(([featureName, featureRoutes]) => {
|
||||
if (features.isEnabled(featureName)) {
|
||||
return featureRoutes;
|
||||
}
|
||||
const enableFeatureMiddleware = (featureName) => (ctx, next) => {
|
||||
console.log(featureName, features.isEnabled(featureName));
|
||||
if (features.isEnabled(featureName)) {
|
||||
return next();
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
ctx.status = 404;
|
||||
};
|
||||
|
||||
module.exports = [
|
||||
@ -63,5 +60,93 @@ module.exports = [
|
||||
],
|
||||
},
|
||||
},
|
||||
...getFeaturesRoutes(),
|
||||
|
||||
// SSO
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/providers',
|
||||
handler: 'authentication.getProviders',
|
||||
config: {
|
||||
middlewares: [enableFeatureMiddleware('sso')],
|
||||
auth: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/connect/:provider',
|
||||
handler: 'authentication.providerLogin',
|
||||
config: {
|
||||
middlewares: [enableFeatureMiddleware('sso')],
|
||||
auth: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
method: 'POST',
|
||||
path: '/connect/:provider',
|
||||
handler: 'authentication.providerLogin',
|
||||
config: {
|
||||
middlewares: [enableFeatureMiddleware('sso')],
|
||||
auth: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/providers/options',
|
||||
handler: 'authentication.getProviderLoginOptions',
|
||||
config: {
|
||||
middlewares: [enableFeatureMiddleware('sso')],
|
||||
policies: [
|
||||
'admin::isAuthenticatedAdmin',
|
||||
{ name: 'admin::hasPermissions', config: { actions: ['admin::provider-login.read'] } },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
method: 'PUT',
|
||||
path: '/providers/options',
|
||||
handler: 'authentication.updateProviderLoginOptions',
|
||||
config: {
|
||||
middlewares: [enableFeatureMiddleware('sso')],
|
||||
policies: [
|
||||
'admin::isAuthenticatedAdmin',
|
||||
{ name: 'admin::hasPermissions', config: { actions: ['admin::provider-login.update'] } },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// Audit logs
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/audit-logs',
|
||||
handler: 'auditLogs.findMany',
|
||||
config: {
|
||||
middlewares: [enableFeatureMiddleware('audit-logs')],
|
||||
policies: [
|
||||
'admin::isAuthenticatedAdmin',
|
||||
{
|
||||
name: 'admin::hasPermissions',
|
||||
config: {
|
||||
actions: ['admin::audit-logs.read'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/audit-logs/:id',
|
||||
handler: 'auditLogs.findOne',
|
||||
config: {
|
||||
middlewares: [enableFeatureMiddleware('audit-logs')],
|
||||
policies: [
|
||||
'admin::isAuthenticatedAdmin',
|
||||
{
|
||||
name: 'admin::hasPermissions',
|
||||
config: {
|
||||
actions: ['admin::audit-logs.read'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
@ -6,6 +6,28 @@ const { features } = require('@strapi/strapi/lib/utils/ee');
|
||||
const createLocalStrategy = require('../../../server/services/passport/local-strategy');
|
||||
const sso = require('./passport/sso');
|
||||
|
||||
// wrap functions with feature flag to allow execute code lazyly
|
||||
// Looking at the code wrapped we probably can just add a condition in the functions
|
||||
const wrapWithFeatureFlag = (flag, obj) => {
|
||||
const newObj = {};
|
||||
|
||||
Object.keys(obj).forEach((key) => {
|
||||
if (typeof obj[key] === 'function') {
|
||||
newObj[key] = (...args) => {
|
||||
if (!features.isEnabled(flag)) {
|
||||
throw new Error(`${key} cannot be executed`);
|
||||
}
|
||||
|
||||
return obj[key].apply(newObj, ...args);
|
||||
};
|
||||
} else {
|
||||
newObj[key] = obj[key];
|
||||
}
|
||||
});
|
||||
|
||||
return newObj;
|
||||
};
|
||||
|
||||
const getPassportStrategies = () => {
|
||||
const localStrategy = createLocalStrategy(strapi);
|
||||
|
||||
@ -25,8 +47,5 @@ const getPassportStrategies = () => {
|
||||
|
||||
module.exports = {
|
||||
getPassportStrategies,
|
||||
...wrapWithFeatureFlag('sso', sso),
|
||||
};
|
||||
|
||||
if (features.isEnabled('sso')) {
|
||||
Object.assign(module.exports, sso);
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ const defaultFeatures = {
|
||||
gold: ['sso'],
|
||||
};
|
||||
|
||||
module.exports = ({ dir, logger = noLog }) => {
|
||||
const EEService = ({ dir, logger = noLog }) => {
|
||||
if (_.has(internals, 'isEE')) return internals.isEE;
|
||||
|
||||
const warnAndReturn = (msg = 'Invalid license. Starting in CE.') => {
|
||||
@ -49,6 +49,8 @@ module.exports = ({ dir, logger = noLog }) => {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: optimistically return true if license key is valid
|
||||
|
||||
try {
|
||||
const plainLicense = Buffer.from(license, 'base64').toString();
|
||||
const [signatureb64, contentb64] = plainLicense.split('\n');
|
||||
@ -64,6 +66,8 @@ module.exports = ({ dir, logger = noLog }) => {
|
||||
if (!isValid) return warnAndReturn();
|
||||
|
||||
internals.licenseInfo = JSON.parse(content);
|
||||
internals.licenseInfo.features =
|
||||
internals.licenseInfo.features || defaultFeatures[internals.licenseInfo.type];
|
||||
|
||||
const expirationTime = new Date(internals.licenseInfo.expireAt).getTime();
|
||||
if (expirationTime < new Date().getTime()) {
|
||||
@ -77,7 +81,14 @@ module.exports = ({ dir, logger = noLog }) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
Object.defineProperty(module.exports, 'licenseInfo', {
|
||||
EEService.checkLicense = async () => {
|
||||
// TODO: online / DB check of the license info
|
||||
// TODO: refresh info if the DB info is outdated
|
||||
// TODO: register cron
|
||||
// internals.licenseInfo = await db.getLicense();
|
||||
};
|
||||
|
||||
Object.defineProperty(EEService, 'licenseInfo', {
|
||||
get() {
|
||||
mustHaveKey('licenseInfo');
|
||||
return internals.licenseInfo;
|
||||
@ -86,7 +97,7 @@ Object.defineProperty(module.exports, 'licenseInfo', {
|
||||
enumerable: false,
|
||||
});
|
||||
|
||||
Object.defineProperty(module.exports, 'isEE', {
|
||||
Object.defineProperty(EEService, 'isEE', {
|
||||
get() {
|
||||
mustHaveKey('isEE');
|
||||
return internals.isEE;
|
||||
@ -95,20 +106,14 @@ Object.defineProperty(module.exports, 'isEE', {
|
||||
enumerable: false,
|
||||
});
|
||||
|
||||
Object.defineProperty(module.exports, 'features', {
|
||||
Object.defineProperty(EEService, 'features', {
|
||||
get() {
|
||||
const licenseInfo = module.exports.licenseInfo;
|
||||
|
||||
const { type: licenseType } = module.exports.licenseInfo;
|
||||
|
||||
const features = licenseInfo.features || defaultFeatures[licenseType];
|
||||
|
||||
return {
|
||||
isEnabled(feature) {
|
||||
return features.includes(feature);
|
||||
return internals.licenseInfo.features.includes(feature);
|
||||
},
|
||||
getEnabled() {
|
||||
return features;
|
||||
return internals.licenseInfo.features;
|
||||
},
|
||||
};
|
||||
},
|
||||
@ -123,3 +128,5 @@ const mustHaveKey = (key) => {
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = EEService;
|
||||
|
@ -365,6 +365,8 @@ class Strapi {
|
||||
}
|
||||
|
||||
async register() {
|
||||
await this.loadEE();
|
||||
|
||||
await Promise.all([
|
||||
this.loadApp(),
|
||||
this.loadSanitizers(),
|
||||
@ -455,6 +457,8 @@ class Strapi {
|
||||
value: strapi.contentTypes,
|
||||
});
|
||||
|
||||
await ee.checkLicense();
|
||||
|
||||
await this.startWebhooks();
|
||||
|
||||
await this.server.initMiddlewares();
|
||||
@ -480,7 +484,6 @@ class Strapi {
|
||||
}
|
||||
|
||||
async load() {
|
||||
await this.loadEE();
|
||||
await this.register();
|
||||
await this.bootstrap();
|
||||
|
||||
|
@ -47,6 +47,7 @@ const createTelemetryInstance = (strapi) => {
|
||||
}
|
||||
},
|
||||
bootstrap() {
|
||||
// TODO: remove
|
||||
if (strapi.EE === true && ee.isEE === true) {
|
||||
const pingDisabled =
|
||||
isTruthy(process.env.STRAPI_LICENSE_PING_DISABLED) && ee.licenseInfo.type === 'gold';
|
||||
|
Loading…
x
Reference in New Issue
Block a user