mirror of
https://github.com/strapi/strapi.git
synced 2025-09-14 19:19:43 +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);
|
await actionProvider.registerMany(actions.auditLogs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: check admin seats
|
||||||
|
|
||||||
await executeCEBootstrap();
|
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';
|
'use strict';
|
||||||
|
|
||||||
// eslint-disable-next-line node/no-extraneous-require
|
|
||||||
const { features } = require('@strapi/strapi/lib/utils/ee');
|
const { features } = require('@strapi/strapi/lib/utils/ee');
|
||||||
const featuresRoutes = require('./features-routes');
|
|
||||||
|
|
||||||
const getFeaturesRoutes = () => {
|
const enableFeatureMiddleware = (featureName) => (ctx, next) => {
|
||||||
return Object.entries(featuresRoutes).flatMap(([featureName, featureRoutes]) => {
|
console.log(featureName, features.isEnabled(featureName));
|
||||||
if (features.isEnabled(featureName)) {
|
if (features.isEnabled(featureName)) {
|
||||||
return featureRoutes;
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
ctx.status = 404;
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = [
|
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 createLocalStrategy = require('../../../server/services/passport/local-strategy');
|
||||||
const sso = require('./passport/sso');
|
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 getPassportStrategies = () => {
|
||||||
const localStrategy = createLocalStrategy(strapi);
|
const localStrategy = createLocalStrategy(strapi);
|
||||||
|
|
||||||
@ -25,8 +47,5 @@ const getPassportStrategies = () => {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getPassportStrategies,
|
getPassportStrategies,
|
||||||
|
...wrapWithFeatureFlag('sso', sso),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (features.isEnabled('sso')) {
|
|
||||||
Object.assign(module.exports, sso);
|
|
||||||
}
|
|
||||||
|
@ -21,7 +21,7 @@ const defaultFeatures = {
|
|||||||
gold: ['sso'],
|
gold: ['sso'],
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = ({ dir, logger = noLog }) => {
|
const EEService = ({ dir, logger = noLog }) => {
|
||||||
if (_.has(internals, 'isEE')) return internals.isEE;
|
if (_.has(internals, 'isEE')) return internals.isEE;
|
||||||
|
|
||||||
const warnAndReturn = (msg = 'Invalid license. Starting in CE.') => {
|
const warnAndReturn = (msg = 'Invalid license. Starting in CE.') => {
|
||||||
@ -49,6 +49,8 @@ module.exports = ({ dir, logger = noLog }) => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: optimistically return true if license key is valid
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const plainLicense = Buffer.from(license, 'base64').toString();
|
const plainLicense = Buffer.from(license, 'base64').toString();
|
||||||
const [signatureb64, contentb64] = plainLicense.split('\n');
|
const [signatureb64, contentb64] = plainLicense.split('\n');
|
||||||
@ -64,6 +66,8 @@ module.exports = ({ dir, logger = noLog }) => {
|
|||||||
if (!isValid) return warnAndReturn();
|
if (!isValid) return warnAndReturn();
|
||||||
|
|
||||||
internals.licenseInfo = JSON.parse(content);
|
internals.licenseInfo = JSON.parse(content);
|
||||||
|
internals.licenseInfo.features =
|
||||||
|
internals.licenseInfo.features || defaultFeatures[internals.licenseInfo.type];
|
||||||
|
|
||||||
const expirationTime = new Date(internals.licenseInfo.expireAt).getTime();
|
const expirationTime = new Date(internals.licenseInfo.expireAt).getTime();
|
||||||
if (expirationTime < new Date().getTime()) {
|
if (expirationTime < new Date().getTime()) {
|
||||||
@ -77,7 +81,14 @@ module.exports = ({ dir, logger = noLog }) => {
|
|||||||
return true;
|
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() {
|
get() {
|
||||||
mustHaveKey('licenseInfo');
|
mustHaveKey('licenseInfo');
|
||||||
return internals.licenseInfo;
|
return internals.licenseInfo;
|
||||||
@ -86,7 +97,7 @@ Object.defineProperty(module.exports, 'licenseInfo', {
|
|||||||
enumerable: false,
|
enumerable: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(module.exports, 'isEE', {
|
Object.defineProperty(EEService, 'isEE', {
|
||||||
get() {
|
get() {
|
||||||
mustHaveKey('isEE');
|
mustHaveKey('isEE');
|
||||||
return internals.isEE;
|
return internals.isEE;
|
||||||
@ -95,20 +106,14 @@ Object.defineProperty(module.exports, 'isEE', {
|
|||||||
enumerable: false,
|
enumerable: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(module.exports, 'features', {
|
Object.defineProperty(EEService, 'features', {
|
||||||
get() {
|
get() {
|
||||||
const licenseInfo = module.exports.licenseInfo;
|
|
||||||
|
|
||||||
const { type: licenseType } = module.exports.licenseInfo;
|
|
||||||
|
|
||||||
const features = licenseInfo.features || defaultFeatures[licenseType];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isEnabled(feature) {
|
isEnabled(feature) {
|
||||||
return features.includes(feature);
|
return internals.licenseInfo.features.includes(feature);
|
||||||
},
|
},
|
||||||
getEnabled() {
|
getEnabled() {
|
||||||
return features;
|
return internals.licenseInfo.features;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -123,3 +128,5 @@ const mustHaveKey = (key) => {
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports = EEService;
|
||||||
|
@ -365,6 +365,8 @@ class Strapi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async register() {
|
async register() {
|
||||||
|
await this.loadEE();
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.loadApp(),
|
this.loadApp(),
|
||||||
this.loadSanitizers(),
|
this.loadSanitizers(),
|
||||||
@ -455,6 +457,8 @@ class Strapi {
|
|||||||
value: strapi.contentTypes,
|
value: strapi.contentTypes,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await ee.checkLicense();
|
||||||
|
|
||||||
await this.startWebhooks();
|
await this.startWebhooks();
|
||||||
|
|
||||||
await this.server.initMiddlewares();
|
await this.server.initMiddlewares();
|
||||||
@ -480,7 +484,6 @@ class Strapi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
await this.loadEE();
|
|
||||||
await this.register();
|
await this.register();
|
||||||
await this.bootstrap();
|
await this.bootstrap();
|
||||||
|
|
||||||
|
@ -47,6 +47,7 @@ const createTelemetryInstance = (strapi) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
bootstrap() {
|
bootstrap() {
|
||||||
|
// TODO: remove
|
||||||
if (strapi.EE === true && ee.isEE === true) {
|
if (strapi.EE === true && ee.isEE === true) {
|
||||||
const pingDisabled =
|
const pingDisabled =
|
||||||
isTruthy(process.env.STRAPI_LICENSE_PING_DISABLED) && ee.licenseInfo.type === 'gold';
|
isTruthy(process.env.STRAPI_LICENSE_PING_DISABLED) && ee.licenseInfo.type === 'gold';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user