diff --git a/src/main/java/io/supertokens/Main.java b/src/main/java/io/supertokens/Main.java index dbeccaa34..467db6781 100644 --- a/src/main/java/io/supertokens/Main.java +++ b/src/main/java/io/supertokens/Main.java @@ -27,6 +27,7 @@ import io.supertokens.cronjobs.deleteExpiredPasswordlessDevices.DeleteExpiredPasswordlessDevices; import io.supertokens.cronjobs.deleteExpiredSessions.DeleteExpiredSessions; import io.supertokens.cronjobs.deleteExpiredTotpTokens.DeleteExpiredTotpTokens; +import io.supertokens.cronjobs.syncCoreConfigWithDb.SyncCoreConfigWithDb; import io.supertokens.cronjobs.telemetry.Telemetry; import io.supertokens.emailpassword.PasswordHashing; import io.supertokens.exceptions.QuitProgramException; @@ -227,6 +228,8 @@ private void init() throws IOException, StorageQueryException { // starts removing old session cronjob List> uniqueUserPoolIdsTenants = StorageLayer.getTenantsWithUniqueUserPoolId(this); + Cronjobs.addCronjob(this, SyncCoreConfigWithDb.init(this)); + Cronjobs.addCronjob(this, DeleteExpiredSessions.init(this, uniqueUserPoolIdsTenants)); // starts removing old password reset tokens diff --git a/src/main/java/io/supertokens/cronjobs/syncCoreConfigWithDb/SyncCoreConfigWithDb.java b/src/main/java/io/supertokens/cronjobs/syncCoreConfigWithDb/SyncCoreConfigWithDb.java new file mode 100644 index 000000000..ba74509be --- /dev/null +++ b/src/main/java/io/supertokens/cronjobs/syncCoreConfigWithDb/SyncCoreConfigWithDb.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.cronjobs.syncCoreConfigWithDb; + +import io.supertokens.Main; +import io.supertokens.cronjobs.CronTask; +import io.supertokens.cronjobs.CronTaskTest; +import io.supertokens.cronjobs.deleteExpiredSessions.DeleteExpiredSessions; +import io.supertokens.multitenancy.Multitenancy; +import io.supertokens.multitenancy.MultitenancyHelper; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; + +import java.util.List; + +public class SyncCoreConfigWithDb extends CronTask { + + public static final String RESOURCE_KEY = "io.supertokens.cronjobs.syncCoreConfigWithDb.SyncCoreConfigWithDb"; + + Main main; + + private SyncCoreConfigWithDb(Main main) { + super("SyncCoreConfigWithDb", main, TenantIdentifier.BASE_TENANT); + this.main = main; + } + + public static SyncCoreConfigWithDb init(Main main) { + return (SyncCoreConfigWithDb) main.getResourceDistributor() + .setResource(TenantIdentifier.BASE_TENANT, RESOURCE_KEY, + new SyncCoreConfigWithDb(main)); + } + + @Override + public int getIntervalTimeSeconds() { + if (Main.isTesting) { + Integer interval = CronTaskTest.getInstance(main).getIntervalInSeconds(RESOURCE_KEY); + if (interval != null) { + return interval; + } + } + return 60; + } + + @Override + public int getInitialWaitTimeSeconds() { + if (Main.isTesting) { + return 0; + } + return 60; + } + + @Override + protected void doTaskForTargetTenant(TenantIdentifier targetTenant) throws Exception { + MultitenancyHelper.getInstance(main).refreshTenantsInCoreBasedOnChangesInCoreConfigOrIfTenantListChanged(true); + } +} diff --git a/src/test/java/io/supertokens/test/CronjobTest.java b/src/test/java/io/supertokens/test/CronjobTest.java index 99e11953c..5578580f7 100644 --- a/src/test/java/io/supertokens/test/CronjobTest.java +++ b/src/test/java/io/supertokens/test/CronjobTest.java @@ -20,11 +20,15 @@ import io.supertokens.Main; import io.supertokens.ProcessState; import io.supertokens.cronjobs.CronTask; +import io.supertokens.cronjobs.CronTaskTest; import io.supertokens.cronjobs.Cronjobs; +import io.supertokens.cronjobs.deleteExpiredDashboardSessions.DeleteExpiredDashboardSessions; +import io.supertokens.cronjobs.syncCoreConfigWithDb.SyncCoreConfigWithDb; import io.supertokens.exceptions.QuitProgramException; import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.multitenancy.Multitenancy; +import io.supertokens.multitenancy.MultitenancyHelper; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.multitenancy.*; @@ -699,4 +703,82 @@ public void testPerUserPoolCronTask() throws Exception { process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + + @Test + public void testThatCoreAutomaticallySyncsToConfigChangesInDb() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{EE_FEATURES.MULTI_TENANCY}); + CronTaskTest.getInstance(process.getProcess()).setIntervalInSeconds(SyncCoreConfigWithDb.RESOURCE_KEY, + 3); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + if (StorageLayer.isInMemDb(process.getProcess())) { + return; + } + + TenantIdentifier t1 = new TenantIdentifier(null, "a1", null); + Multitenancy.addNewOrUpdateAppOrTenant(process.getProcess(), new TenantConfig( + t1, + new EmailPasswordConfig(false), + new ThirdPartyConfig(false, null), + new PasswordlessConfig(false), + new JsonObject() + ), false); + + boolean found = false; + + TenantConfig[] allTenants = MultitenancyHelper.getInstance(process.getProcess()).getAllTenants(); + for (TenantConfig tenant : allTenants) { + if (tenant.tenantIdentifier.equals(t1)) { + assertFalse(tenant.emailPasswordConfig.enabled); + found = true; + } + } + assertTrue(found); + + MultitenancyStorage storage = (MultitenancyStorage) StorageLayer.getStorage(process.getProcess()); + storage.overwriteTenantConfig(new TenantConfig( + t1, + new EmailPasswordConfig(true), + new ThirdPartyConfig(false, null), + new PasswordlessConfig(false), + new JsonObject() + )); + + // Check that it was not updated in memory yet + found = false; + allTenants = MultitenancyHelper.getInstance(process.getProcess()).getAllTenants(); + for (TenantConfig tenant : allTenants) { + if (tenant.tenantIdentifier.equals(t1)) { + assertFalse(tenant.emailPasswordConfig.enabled); + found = true; + } + } + assertTrue(found); + + // Wait for the cronjob to run + Thread.sleep(3100); + + // Check that it was updated in memory by now + found = false; + allTenants = MultitenancyHelper.getInstance(process.getProcess()).getAllTenants(); + for (TenantConfig tenant : allTenants) { + if (tenant.tenantIdentifier.equals(t1)) { + assertTrue(tenant.emailPasswordConfig.enabled); + found = true; + } + } + assertTrue(found); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } }