Skip to content

Commit

Permalink
Add cron job to dynamically update the license
Browse files Browse the repository at this point in the history
  • Loading branch information
fdel-car committed Jan 25, 2023
1 parent b37a8be commit 2aecf83
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 41 deletions.
86 changes: 46 additions & 40 deletions packages/core/strapi/ee/index.js
Expand Up @@ -7,6 +7,14 @@ const fetch = require('node-fetch');
const { pick } = require('lodash/fp');

const { coreStoreModel } = require('../lib/services/core-store');
const { getRecurringCronExpression } = require('../lib/utils/cron');

const ONE_MINUTE = 1000 * 60;
const DEFAULT_FEATURES = {
bronze: [],
silver: [],
gold: ['sso'],
};

const publicKey = fs.readFileSync(join(__dirname, 'resources/key.pub'));

Expand Down Expand Up @@ -34,30 +42,33 @@ const readLicense = (directory) => {
};

const fetchLicense = async (key, fallback) => {
const fallbackToStoredLicense = () => {
const error = 'Could not proceed to the online verification of your license.';

if (fallback) {
ee.logger(`${error} We will try to use the locally stored one as a potential fallback.`);
return fallback;
}

disable(`${error} Sorry for the inconvenience. Starting in CE.`);
return null;
};

try {
const response = await fetch(`https://license.strapi.io/api/licenses/${key}`);

if (response.status !== 200) {
if (response.status >= 500) {
return fallbackToStoredLicense();
}

if (response.status >= 400) {
disable();
return null;
}

return response.text();
} catch (error) {
if (error instanceof fetch.FetchError) {
if (fallback) {
ee.logger(
'Could not proceed to the online verification of your license. We will try to use your locally stored one as a potential fallback.'
);
return fallback;
}

disable(
'Could not proceed to the online verification of your license, sorry for the inconvenience. Starting in CE.'
);
}

return null;
} catch {
return fallbackToStoredLicense();
}
};

Expand Down Expand Up @@ -103,8 +114,6 @@ const init = (licenseDir, logger) => {
}
};

const oneMinute = 1000 * 60;

const onlineUpdate = async (db) => {
const transaction = await db.transaction();

Expand All @@ -120,7 +129,7 @@ const onlineUpdate = async (db) => {
.execute()
.then((result) => (result ? JSON.parse(result.value) : result));

const useStoredLicense = eeInfo?.lastOnlineCheck > Date.now() - oneMinute;
const useStoredLicense = eeInfo?.lastOnlineCheck > Date.now() - ONE_MINUTE;
const license = useStoredLicense
? eeInfo.license
: await fetchLicense(ee.licenseInfo.licenseKey, eeInfo?.license);
Expand Down Expand Up @@ -152,20 +161,13 @@ const onlineUpdate = async (db) => {

await transaction.commit();
} catch (error) {
// TODO: The database can be locked at the time of writing, could just a SQLite issue only
// The database can be locked at the time of writing, seems to just be a SQLite issue
await transaction.rollback();
return disable(error.message);
}
};

const defaultFeatures = {
bronze: [],
silver: [],
gold: ['sso'],
};

const validateInfo = () => {
if (ee.licenseInfo.expireAt) {
if (!ee.licenseInfo.expireAt) {
return;
}

Expand All @@ -178,34 +180,38 @@ const validateInfo = () => {
ee.enabled = true;

if (!ee.licenseInfo.features) {
ee.licenseInfo.features = defaultFeatures[ee.licenseInfo.type];
ee.licenseInfo.features = DEFAULT_FEATURES[ee.licenseInfo.type];
}
};

const recurringCheck = async ({ strapi }) => {
await onlineUpdate(strapi.db);
validateInfo();
};

// This env variable support is temporary to ease the migration between online vs offline
const shouldStayOffline = process.env.STRAPI_DISABLE_LICENSE_PING?.toLowerCase() === 'true';

const checkLicense = async (db) => {
const checkLicense = async ({ strapi }) => {
if (!shouldStayOffline) {
await onlineUpdate(db);
// TODO: Register cron, try to spread it out across projects to avoid regular request spikes
await onlineUpdate(strapi.db);
const now = new Date();
strapi.cron.add({ [getRecurringCronExpression(now)]: recurringCheck });
} else if (!ee.licenseInfo.expireAt) {
return disable('Your license does not have offline support. Starting in CE.');
}

if (ee.enabled) {
validateInfo();
}
validateInfo();
};

module.exports = {
init,
disable,
features: {
isEnabled: (feature) => (ee.enabled && ee.licenseInfo.features?.includes(feature)) || false,
getEnabled: () => (ee.enabled && Object.freeze(ee.licenseInfo.features)) || [],
},
checkLicense,
get isEE() {
return ee.enabled;
},
features: {
isEnabled: (feature) => (ee.enabled && ee.licenseInfo.features?.includes(feature)) || false,
getEnabled: () => (ee.enabled && Object.freeze(ee.licenseInfo.features)) || [],
},
};
2 changes: 1 addition & 1 deletion packages/core/strapi/lib/Strapi.js
Expand Up @@ -450,7 +450,7 @@ class Strapi {
await this.db.schema.sync();

if (this.EE) {
await ee.checkLicense(this.db);
await ee.checkLicense({ strapi: this });
}

await this.hook('strapi::content-types.afterSync').call({
Expand Down
16 changes: 16 additions & 0 deletions packages/core/strapi/lib/utils/cron.js
@@ -0,0 +1,16 @@
'use strict';

const shiftHours = (date, step) => {
const frequency = 24 / step;
const list = Array.from({ length: frequency }, (_, index) => index * step);
const hour = date.getHours();
return list.map((value) => (value + hour) % 24).sort((a, b) => a - b);
};

// TODO: This should be transformed into a cron expression shifter that could be reused in other places
// For now it's tailored to the license check cron, scheduled every 12h
const getRecurringCronExpression = (date) => `${date.getMinutes()} ${shiftHours(date, 12)} * * *`;

module.exports = {
getRecurringCronExpression,
};

0 comments on commit 2aecf83

Please sign in to comment.