From 847e3129151bef631435825d3afb1f9d21c723ff Mon Sep 17 00:00:00 2001
From: Christopher Kolstad <chriswk@getunleash.io>
Date: Fri, 29 Nov 2024 13:28:39 +0100
Subject: [PATCH 1/9] Reapply "task: enabled in OSS." (#8892)

This reverts commit 3c01813826e8d9c9ccb750eaa01017b7b59f28dd.
---
 .../__snapshots__/create-config.test.ts.snap  |  1 +
 src/lib/create-config.ts                      |  5 ++
 src/lib/db/feature-environment-store.ts       | 31 ++++++-
 src/lib/db/index.ts                           | 14 +---
 .../features/access/createAccessService.ts    |  2 +-
 .../api-tokens/createApiTokenService.ts       |  2 +-
 .../client-feature-toggle-store.ts            | 18 +++-
 .../createClientFeatureToggleService.ts       | 13 +--
 .../createExportImportService.ts              |  9 +-
 .../createFeatureLifecycle.ts                 |  4 +-
 .../createFeatureToggleService.ts             | 14 +---
 .../feature-toggle/feature-toggle-store.ts    | 10 +++
 .../createInstanceStatsService.ts             |  9 +-
 .../createEnvironmentService.ts               | 11 +--
 .../project-environments/environment-store.ts | 37 +++++++--
 .../createProjectInsightsService.ts           |  7 +-
 .../createProjectStatusService.ts             |  7 +-
 .../features/project/createProjectService.ts  | 11 +--
 src/lib/features/project/project-store.ts     | 16 +++-
 src/lib/types/option.ts                       |  2 +
 .../e2e/api/admin/environment-oss.e2e.test.ts | 68 +++++++++++++++
 .../e2e/api/client/feature-oss.e2e.test.ts    | 82 +++++++++++++++++++
 22 files changed, 284 insertions(+), 89 deletions(-)
 create mode 100644 src/test/e2e/api/admin/environment-oss.e2e.test.ts
 create mode 100644 src/test/e2e/api/client/feature-oss.e2e.test.ts

diff --git a/src/lib/__snapshots__/create-config.test.ts.snap b/src/lib/__snapshots__/create-config.test.ts.snap
index d056cc87f375..6248cd1cfb8c 100644
--- a/src/lib/__snapshots__/create-config.test.ts.snap
+++ b/src/lib/__snapshots__/create-config.test.ts.snap
@@ -85,6 +85,7 @@ exports[`should create default config 1`] = `
   },
   "inlineSegmentConstraints": true,
   "isEnterprise": false,
+  "isOss": false,
   "listen": {
     "host": undefined,
     "port": 4242,
diff --git a/src/lib/create-config.ts b/src/lib/create-config.ts
index bfd59d732458..015e7a9973ac 100644
--- a/src/lib/create-config.ts
+++ b/src/lib/create-config.ts
@@ -620,6 +620,10 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig {
         Boolean(options.enterpriseVersion) &&
         ui.environment?.toLowerCase() !== 'pro';
 
+    const isTest = process.env.NODE_ENV === 'test';
+    const isOss = isTest
+        ? options.isOss || false
+        : !isEnterprise && ui.environment !== 'pro';
     const metricsRateLimiting = loadMetricsRateLimitingConfig(options);
 
     const rateLimiting = loadRateLimitingConfig(options);
@@ -760,6 +764,7 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig {
         publicFolder: options.publicFolder,
         disableScheduler: options.disableScheduler,
         isEnterprise: isEnterprise,
+        isOss: isOss,
         metricsRateLimiting,
         rateLimiting,
         feedbackUriPath,
diff --git a/src/lib/db/feature-environment-store.ts b/src/lib/db/feature-environment-store.ts
index 6b3cb7392539..3bb80159bff9 100644
--- a/src/lib/db/feature-environment-store.ts
+++ b/src/lib/db/feature-environment-store.ts
@@ -3,13 +3,14 @@ import type {
     FeatureEnvironmentKey,
     IFeatureEnvironmentStore,
 } from '../types/stores/feature-environment-store';
-import type { Logger, LogProvider } from '../logger';
+import type { Logger } from '../logger';
 import metricsHelper from '../util/metrics-helper';
 import { DB_TIME } from '../metric-events';
 import type { IFeatureEnvironment, IVariant } from '../types/model';
 import NotFoundError from '../error/notfound-error';
 import { v4 as uuidv4 } from 'uuid';
 import type { Db } from './db';
+import type { IUnleashConfig } from '../types';
 
 const T = {
     featureEnvs: 'feature_environments',
@@ -36,7 +37,12 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
 
     private readonly timer: Function;
 
-    constructor(db: Db, eventBus: EventEmitter, getLogger: LogProvider) {
+    private readonly isOss: boolean;
+    constructor(
+        db: Db,
+        eventBus: EventEmitter,
+        { getLogger, isOss }: Pick<IUnleashConfig, 'getLogger' | 'isOss'>,
+    ) {
         this.db = db;
         this.logger = getLogger('feature-environment-store.ts');
         this.timer = (action) =>
@@ -44,6 +50,7 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
                 store: 'feature-environments',
                 action,
             });
+        this.isOss = isOss;
     }
 
     async delete({
@@ -96,11 +103,30 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
         );
     }
 
+    addOssFilterIfNeeded(queryBuilder) {
+        if (this.isOss) {
+            return queryBuilder
+                .join(
+                    'environments',
+                    'environments.name',
+                    '=',
+                    `${T.featureEnvs}.environment`,
+                )
+                .whereIn('environments.name', [
+                    'default',
+                    'development',
+                    'production',
+                ]);
+        }
+        return queryBuilder;
+    }
+
     async getAll(query?: Object): Promise<IFeatureEnvironment[]> {
         let rows = this.db(T.featureEnvs);
         if (query) {
             rows = rows.where(query);
         }
+        this.addOssFilterIfNeeded(rows);
         return (await rows).map((r) => ({
             enabled: r.enabled,
             featureName: r.feature_name,
@@ -119,6 +145,7 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
         if (environment) {
             rows = rows.where({ environment });
         }
+        this.addOssFilterIfNeeded(rows);
         return (await rows).map((r) => ({
             enabled: r.enabled,
             featureName: r.feature_name,
diff --git a/src/lib/db/index.ts b/src/lib/db/index.ts
index 72ddcd4b497b..3722d91d46fd 100644
--- a/src/lib/db/index.ts
+++ b/src/lib/db/index.ts
@@ -94,12 +94,7 @@ export const createStores = (
         settingStore: new SettingStore(db, getLogger),
         userStore: new UserStore(db, getLogger, config.flagResolver),
         accountStore: new AccountStore(db, getLogger),
-        projectStore: new ProjectStore(
-            db,
-            eventBus,
-            getLogger,
-            config.flagResolver,
-        ),
+        projectStore: new ProjectStore(db, eventBus, config),
         tagStore: new TagStore(db, eventBus, getLogger),
         tagTypeStore: new TagTypeStore(db, eventBus, getLogger),
         addonStore: new AddonStore(db, eventBus, getLogger),
@@ -122,15 +117,14 @@ export const createStores = (
         clientFeatureToggleStore: new FeatureToggleClientStore(
             db,
             eventBus,
-            getLogger,
-            config.flagResolver,
+            config,
         ),
-        environmentStore: new EnvironmentStore(db, eventBus, getLogger),
+        environmentStore: new EnvironmentStore(db, eventBus, config),
         featureTagStore: new FeatureTagStore(db, eventBus, getLogger),
         featureEnvironmentStore: new FeatureEnvironmentStore(
             db,
             eventBus,
-            getLogger,
+            config,
         ),
         userSplashStore: new UserSplashStore(db, eventBus, getLogger),
         roleStore: new RoleStore(db, eventBus, getLogger),
diff --git a/src/lib/features/access/createAccessService.ts b/src/lib/features/access/createAccessService.ts
index 26791fc82fac..c381c0ed278c 100644
--- a/src/lib/features/access/createAccessService.ts
+++ b/src/lib/features/access/createAccessService.ts
@@ -25,7 +25,7 @@ export const createAccessService = (
     const groupStore = new GroupStore(db);
     const accountStore = new AccountStore(db, getLogger);
     const roleStore = new RoleStore(db, eventBus, getLogger);
-    const environmentStore = new EnvironmentStore(db, eventBus, getLogger);
+    const environmentStore = new EnvironmentStore(db, eventBus, config);
     const accessStore = new AccessStore(db, eventBus, getLogger);
     const eventService = createEventsService(db, config);
     const groupService = new GroupService(
diff --git a/src/lib/features/api-tokens/createApiTokenService.ts b/src/lib/features/api-tokens/createApiTokenService.ts
index 18b236c628c8..0dec7de7a457 100644
--- a/src/lib/features/api-tokens/createApiTokenService.ts
+++ b/src/lib/features/api-tokens/createApiTokenService.ts
@@ -21,7 +21,7 @@ export const createApiTokenService = (
         getLogger,
         config.flagResolver,
     );
-    const environmentStore = new EnvironmentStore(db, eventBus, getLogger);
+    const environmentStore = new EnvironmentStore(db, eventBus, config);
     const eventService = createEventsService(db, config);
 
     return new ApiTokenService(
diff --git a/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts b/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts
index b93d94daeeda..400ff737d507 100644
--- a/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts
+++ b/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts
@@ -1,7 +1,7 @@
 import { Knex } from 'knex';
 import metricsHelper from '../../util/metrics-helper';
 import { DB_TIME } from '../../metric-events';
-import type { Logger, LogProvider } from '../../logger';
+import type { Logger } from '../../logger';
 import type {
     IFeatureToggleClient,
     IFeatureToggleClientStore,
@@ -9,6 +9,7 @@ import type {
     IFlagResolver,
     IStrategyConfig,
     ITag,
+    IUnleashConfig,
     PartialDeep,
 } from '../../types';
 import {
@@ -46,11 +47,16 @@ export default class FeatureToggleClientStore
 
     private flagResolver: IFlagResolver;
 
+    private readonly isOss: boolean;
+
     constructor(
         db: Db,
         eventBus: EventEmitter,
-        getLogger: LogProvider,
-        flagResolver: IFlagResolver,
+        {
+            getLogger,
+            flagResolver,
+            isOss,
+        }: Pick<IUnleashConfig, 'getLogger' | 'flagResolver' | 'isOss'>,
     ) {
         this.db = db;
         this.logger = getLogger('feature-toggle-client-store.ts');
@@ -60,6 +66,7 @@ export default class FeatureToggleClientStore
                 action,
             });
         this.flagResolver = flagResolver;
+        this.isOss = isOss;
     }
 
     private async getAll({
@@ -72,7 +79,6 @@ export default class FeatureToggleClientStore
         const isPlayground = requestType === 'playground';
         const environment = featureQuery?.environment || DEFAULT_ENV;
         const stopTimer = this.timer(`getAllBy${requestType}`);
-
         let selectColumns = [
             'features.name as name',
             'features.description as description',
@@ -103,6 +109,10 @@ export default class FeatureToggleClientStore
 
         let query = this.db('features')
             .modify(FeatureToggleStore.filterByArchived, archived)
+            .modify(
+                FeatureToggleStore.filterByProjectsAccessibleByOss,
+                this.isOss,
+            )
             .leftJoin(
                 this.db('feature_strategies')
                     .select('*')
diff --git a/src/lib/features/client-feature-toggles/createClientFeatureToggleService.ts b/src/lib/features/client-feature-toggles/createClientFeatureToggleService.ts
index f48d98e0d136..17be891a7a7d 100644
--- a/src/lib/features/client-feature-toggles/createClientFeatureToggleService.ts
+++ b/src/lib/features/client-feature-toggles/createClientFeatureToggleService.ts
@@ -11,13 +11,10 @@ export const createClientFeatureToggleService = (
     db: Db,
     config: IUnleashConfig,
 ): ClientFeatureToggleService => {
-    const { getLogger, eventBus, flagResolver } = config;
-
     const featureToggleClientStore = new FeatureToggleClientStore(
         db,
-        eventBus,
-        getLogger,
-        flagResolver,
+        config.eventBus,
+        config,
     );
 
     const segmentReadModel = new SegmentReadModel(db);
@@ -30,7 +27,7 @@ export const createClientFeatureToggleService = (
         },
         segmentReadModel,
         clientFeatureToggleCache,
-        { getLogger, flagResolver },
+        config,
     );
 
     return clientFeatureToggleService;
@@ -39,8 +36,6 @@ export const createClientFeatureToggleService = (
 export const createFakeClientFeatureToggleService = (
     config: IUnleashConfig,
 ): ClientFeatureToggleService => {
-    const { getLogger, flagResolver } = config;
-
     const fakeClientFeatureToggleStore = new FakeClientFeatureToggleStore();
 
     const fakeSegmentReadModel = new FakeSegmentReadModel();
@@ -51,7 +46,7 @@ export const createFakeClientFeatureToggleService = (
         },
         fakeSegmentReadModel,
         null,
-        { getLogger, flagResolver },
+        config,
     );
 
     return clientFeatureToggleService;
diff --git a/src/lib/features/export-import-toggles/createExportImportService.ts b/src/lib/features/export-import-toggles/createExportImportService.ts
index b36b6e0fabf3..69bebf187066 100644
--- a/src/lib/features/export-import-toggles/createExportImportService.ts
+++ b/src/lib/features/export-import-toggles/createExportImportService.ts
@@ -150,12 +150,7 @@ export const deferredExportImportTogglesService = (
         );
         const tagStore = new TagStore(db, eventBus, getLogger);
         const tagTypeStore = new TagTypeStore(db, eventBus, getLogger);
-        const projectStore = new ProjectStore(
-            db,
-            eventBus,
-            getLogger,
-            flagResolver,
-        );
+        const projectStore = new ProjectStore(db, eventBus, config);
         const featureTagStore = new FeatureTagStore(db, eventBus, getLogger);
         const strategyStore = new StrategyStore(db, getLogger);
         const contextFieldStore = new ContextFieldStore(
@@ -172,7 +167,7 @@ export const deferredExportImportTogglesService = (
         const featureEnvironmentStore = new FeatureEnvironmentStore(
             db,
             eventBus,
-            getLogger,
+            config,
         );
         const eventStore = new EventStore(db, getLogger);
         const accessService = createAccessService(db, config);
diff --git a/src/lib/features/feature-lifecycle/createFeatureLifecycle.ts b/src/lib/features/feature-lifecycle/createFeatureLifecycle.ts
index a97b540b6043..ed907e9f894b 100644
--- a/src/lib/features/feature-lifecycle/createFeatureLifecycle.ts
+++ b/src/lib/features/feature-lifecycle/createFeatureLifecycle.ts
@@ -19,11 +19,11 @@ export const createFeatureLifecycleService =
         const { eventBus, getLogger } = config;
         const eventStore = new EventStore(db, getLogger);
         const featureLifecycleStore = new FeatureLifecycleStore(db);
-        const environmentStore = new EnvironmentStore(db, eventBus, getLogger);
+        const environmentStore = new EnvironmentStore(db, eventBus, config);
         const featureEnvironmentStore = new FeatureEnvironmentStore(
             db,
             eventBus,
-            getLogger,
+            config,
         );
         const eventService = createEventsService(db, config);
         const featureLifecycleService = new FeatureLifecycleService(
diff --git a/src/lib/features/feature-toggle/createFeatureToggleService.ts b/src/lib/features/feature-toggle/createFeatureToggleService.ts
index d5d9d21bae87..b12eb3adfc17 100644
--- a/src/lib/features/feature-toggle/createFeatureToggleService.ts
+++ b/src/lib/features/feature-toggle/createFeatureToggleService.ts
@@ -80,19 +80,13 @@ export const createFeatureToggleService = (
     const featureToggleClientStore = new FeatureToggleClientStore(
         db,
         eventBus,
-        getLogger,
-        flagResolver,
-    );
-    const projectStore = new ProjectStore(
-        db,
-        eventBus,
-        getLogger,
-        flagResolver,
+        config,
     );
+    const projectStore = new ProjectStore(db, eventBus, config);
     const featureEnvironmentStore = new FeatureEnvironmentStore(
         db,
         eventBus,
-        getLogger,
+        config,
     );
     const contextFieldStore = new ContextFieldStore(
         db,
@@ -105,7 +99,7 @@ export const createFeatureToggleService = (
     const accessStore = new AccessStore(db, eventBus, getLogger);
     const featureTagStore = new FeatureTagStore(db, eventBus, getLogger);
     const roleStore = new RoleStore(db, eventBus, getLogger);
-    const environmentStore = new EnvironmentStore(db, eventBus, getLogger);
+    const environmentStore = new EnvironmentStore(db, eventBus, config);
     const eventService = createEventsService(db, config);
     const groupService = new GroupService(
         { groupStore, accountStore },
diff --git a/src/lib/features/feature-toggle/feature-toggle-store.ts b/src/lib/features/feature-toggle/feature-toggle-store.ts
index fe412fb9986a..861d1af92c5e 100644
--- a/src/lib/features/feature-toggle/feature-toggle-store.ts
+++ b/src/lib/features/feature-toggle/feature-toggle-store.ts
@@ -439,6 +439,16 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
             : queryBuilder.whereNull('archived_at');
     };
 
+    static filterByProjectsAccessibleByOss: Knex.QueryCallbackWithArgs = (
+        queryBuilder: Knex.QueryBuilder,
+        isOss: boolean,
+    ) => {
+        if (isOss) {
+            return queryBuilder.andWhere('project', '=', 'default');
+        }
+        return queryBuilder;
+    };
+
     rowToFeature(row: FeaturesTable): FeatureToggle {
         if (!row) {
             throw new NotFoundError('No feature flag found');
diff --git a/src/lib/features/instance-stats/createInstanceStatsService.ts b/src/lib/features/instance-stats/createInstanceStatsService.ts
index f1718c149166..fb03d5ec8a1a 100644
--- a/src/lib/features/instance-stats/createInstanceStatsService.ts
+++ b/src/lib/features/instance-stats/createInstanceStatsService.ts
@@ -58,13 +58,8 @@ export const createInstanceStatsService = (db: Db, config: IUnleashConfig) => {
         flagResolver,
     );
     const userStore = new UserStore(db, getLogger, flagResolver);
-    const projectStore = new ProjectStore(
-        db,
-        eventBus,
-        getLogger,
-        flagResolver,
-    );
-    const environmentStore = new EnvironmentStore(db, eventBus, getLogger);
+    const projectStore = new ProjectStore(db, eventBus, config);
+    const environmentStore = new EnvironmentStore(db, eventBus, config);
     const strategyStore = new StrategyStore(db, getLogger);
     const contextFieldStore = new ContextFieldStore(
         db,
diff --git a/src/lib/features/project-environments/createEnvironmentService.ts b/src/lib/features/project-environments/createEnvironmentService.ts
index 9cbcb478c193..9a1dfbd6c626 100644
--- a/src/lib/features/project-environments/createEnvironmentService.ts
+++ b/src/lib/features/project-environments/createEnvironmentService.ts
@@ -21,21 +21,16 @@ export const createEnvironmentService =
         const featureEnvironmentStore = new FeatureEnvironmentStore(
             db,
             eventBus,
-            getLogger,
-        );
-        const projectStore = new ProjectStore(
-            db,
-            eventBus,
-            getLogger,
-            flagResolver,
+            config,
         );
+        const projectStore = new ProjectStore(db, eventBus, config);
         const featureStrategiesStore = new FeatureStrategiesStore(
             db,
             eventBus,
             getLogger,
             flagResolver,
         );
-        const environmentStore = new EnvironmentStore(db, eventBus, getLogger);
+        const environmentStore = new EnvironmentStore(db, eventBus, config);
         const eventService = createEventsService(db, config);
         return new EnvironmentService(
             {
diff --git a/src/lib/features/project-environments/environment-store.ts b/src/lib/features/project-environments/environment-store.ts
index 6838d6eb4a81..3da9522b0018 100644
--- a/src/lib/features/project-environments/environment-store.ts
+++ b/src/lib/features/project-environments/environment-store.ts
@@ -1,6 +1,6 @@
 import type EventEmitter from 'events';
 import type { Db } from '../../db/db';
-import type { Logger, LogProvider } from '../../logger';
+import type { Logger } from '../../logger';
 import metricsHelper from '../../util/metrics-helper';
 import { DB_TIME } from '../../metric-events';
 import type {
@@ -12,6 +12,7 @@ import NotFoundError from '../../error/notfound-error';
 import type { IEnvironmentStore } from './environment-store-type';
 import { snakeCaseKeys } from '../../util/snakeCase';
 import type { CreateFeatureStrategySchema } from '../../openapi';
+import type { IUnleashConfig } from '../../types';
 
 interface IEnvironmentsTable {
     name: string;
@@ -104,11 +105,18 @@ export default class EnvironmentStore implements IEnvironmentStore {
 
     private db: Db;
 
+    private isOss: boolean;
+
     private timer: (string) => any;
 
-    constructor(db: Db, eventBus: EventEmitter, getLogger: LogProvider) {
+    constructor(
+        db: Db,
+        eventBus: EventEmitter,
+        { getLogger, isOss }: Pick<IUnleashConfig, 'getLogger' | 'isOss'>,
+    ) {
         this.db = db;
         this.logger = getLogger('db/environment-store.ts');
+        this.isOss = isOss;
         this.timer = (action) =>
             metricsHelper.wrapTimer(eventBus, DB_TIME, {
                 store: 'environment',
@@ -148,9 +156,15 @@ export default class EnvironmentStore implements IEnvironmentStore {
 
     async get(key: string): Promise<IEnvironment> {
         const stopTimer = this.timer('get');
-        const row = await this.db<IEnvironmentsTable>(TABLE)
-            .where({ name: key })
-            .first();
+        let keyQuery = this.db<IEnvironmentsTable>(TABLE).where({ name: key });
+        if (this.isOss) {
+            keyQuery = keyQuery.whereIn('name', [
+                'default',
+                'development',
+                'production',
+            ]);
+        }
+        const row = await keyQuery.first();
         stopTimer();
         if (row) {
             return mapRow(row);
@@ -169,6 +183,9 @@ export default class EnvironmentStore implements IEnvironmentStore {
         if (query) {
             qB = qB.where(query);
         }
+        if (this.isOss) {
+            qB = qB.whereIn('name', ['default', 'development', 'production']);
+        }
         const rows = await qB;
         stopTimer();
         return rows.map(mapRow);
@@ -196,6 +213,9 @@ export default class EnvironmentStore implements IEnvironmentStore {
         if (query) {
             qB = qB.where(query);
         }
+        if (this.isOss) {
+            qB = qB.whereIn('name', ['default', 'development', 'production']);
+        }
         const rows = await qB;
         stopTimer();
         return rows.map(mapRowWithCounts);
@@ -230,6 +250,13 @@ export default class EnvironmentStore implements IEnvironmentStore {
         if (query) {
             qB = qB.where(query);
         }
+        if (this.isOss) {
+            qB = qB.whereIn('environments.name', [
+                'default',
+                'production',
+                'development',
+            ]);
+        }
 
         const rows = await qB;
         stopTimer();
diff --git a/src/lib/features/project-insights/createProjectInsightsService.ts b/src/lib/features/project-insights/createProjectInsightsService.ts
index 68b46ac3941f..f15c1d61ad07 100644
--- a/src/lib/features/project-insights/createProjectInsightsService.ts
+++ b/src/lib/features/project-insights/createProjectInsightsService.ts
@@ -16,12 +16,7 @@ export const createProjectInsightsService = (
     config: IUnleashConfig,
 ): ProjectInsightsService => {
     const { eventBus, getLogger, flagResolver } = config;
-    const projectStore = new ProjectStore(
-        db,
-        eventBus,
-        getLogger,
-        flagResolver,
-    );
+    const projectStore = new ProjectStore(db, eventBus, config);
     const featureToggleStore = new FeatureToggleStore(
         db,
         eventBus,
diff --git a/src/lib/features/project-status/createProjectStatusService.ts b/src/lib/features/project-status/createProjectStatusService.ts
index c8694775403d..165c641ce8cd 100644
--- a/src/lib/features/project-status/createProjectStatusService.ts
+++ b/src/lib/features/project-status/createProjectStatusService.ts
@@ -24,12 +24,7 @@ export const createProjectStatusService = (
     config: IUnleashConfig,
 ): ProjectStatusService => {
     const eventStore = new EventStore(db, config.getLogger);
-    const projectStore = new ProjectStore(
-        db,
-        config.eventBus,
-        config.getLogger,
-        config.flagResolver,
-    );
+    const projectStore = new ProjectStore(db, config.eventBus, config);
     const apiTokenStore = new ApiTokenStore(
         db,
         config.eventBus,
diff --git a/src/lib/features/project/createProjectService.ts b/src/lib/features/project/createProjectService.ts
index d4a42888eba6..a753ca34224f 100644
--- a/src/lib/features/project/createProjectService.ts
+++ b/src/lib/features/project/createProjectService.ts
@@ -63,12 +63,7 @@ export const createProjectService = (
 ): ProjectService => {
     const { eventBus, getLogger, flagResolver } = config;
     const eventStore = new EventStore(db, getLogger);
-    const projectStore = new ProjectStore(
-        db,
-        eventBus,
-        getLogger,
-        flagResolver,
-    );
+    const projectStore = new ProjectStore(db, eventBus, config);
     const projectOwnersReadModel = new ProjectOwnersReadModel(db);
     const projectFlagCreatorsReadModel = new ProjectFlagCreatorsReadModel(db);
     const groupStore = new GroupStore(db);
@@ -79,11 +74,11 @@ export const createProjectService = (
         flagResolver,
     );
     const accountStore = new AccountStore(db, getLogger);
-    const environmentStore = new EnvironmentStore(db, eventBus, getLogger);
+    const environmentStore = new EnvironmentStore(db, eventBus, config);
     const featureEnvironmentStore = new FeatureEnvironmentStore(
         db,
         eventBus,
-        getLogger,
+        config,
     );
     const projectStatsStore = new ProjectStatsStore(db, eventBus, getLogger);
     const accessService: AccessService = createAccessService(db, config);
diff --git a/src/lib/features/project/project-store.ts b/src/lib/features/project/project-store.ts
index 5df93193c63e..5011159825c4 100644
--- a/src/lib/features/project/project-store.ts
+++ b/src/lib/features/project/project-store.ts
@@ -1,4 +1,4 @@
-import type { Logger, LogProvider } from '../../logger';
+import type { Logger } from '../../logger';
 
 import NotFoundError from '../../error/notfound-error';
 import type {
@@ -8,6 +8,7 @@ import type {
     IProjectApplication,
     IProjectApplications,
     IProjectUpdate,
+    IUnleashConfig,
     ProjectMode,
 } from '../../types';
 import type {
@@ -72,11 +73,16 @@ class ProjectStore implements IProjectStore {
 
     private timer: Function;
 
+    private isOss: boolean;
+
     constructor(
         db: Db,
         eventBus: EventEmitter,
-        getLogger: LogProvider,
-        flagResolver: IFlagResolver,
+        {
+            getLogger,
+            flagResolver,
+            isOss,
+        }: Pick<IUnleashConfig, 'getLogger' | 'flagResolver' | 'isOss'>,
     ) {
         this.db = db;
         this.logger = getLogger('project-store.ts');
@@ -86,6 +92,7 @@ class ProjectStore implements IProjectStore {
                 action,
             });
         this.flagResolver = flagResolver;
+        this.isOss = isOss;
     }
 
     // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
@@ -122,6 +129,9 @@ class ProjectStore implements IProjectStore {
             .orderBy('name', 'asc');
 
         projects = projects.where(`${TABLE}.archived_at`, null);
+        if (this.isOss) {
+            projects = projects.where('id', 'default');
+        }
 
         const rows = await projects;
 
diff --git a/src/lib/types/option.ts b/src/lib/types/option.ts
index 792cb22dc874..b4919ad34152 100644
--- a/src/lib/types/option.ts
+++ b/src/lib/types/option.ts
@@ -143,6 +143,7 @@ export interface IUnleashOptions {
     metricsRateLimiting?: Partial<IMetricsRateLimiting>;
     dailyMetricsStorageDays?: number;
     rateLimiting?: Partial<IRateLimiting>;
+    isOss?: boolean;
     resourceLimits?: Partial<
         Pick<
             ResourceLimitsSchema,
@@ -273,6 +274,7 @@ export interface IUnleashConfig {
     publicFolder?: string;
     disableScheduler?: boolean;
     isEnterprise: boolean;
+    isOss: boolean;
     rateLimiting: IRateLimiting;
     feedbackUriPath?: string;
     openAIAPIKey?: string;
diff --git a/src/test/e2e/api/admin/environment-oss.e2e.test.ts b/src/test/e2e/api/admin/environment-oss.e2e.test.ts
new file mode 100644
index 000000000000..4de671b7d853
--- /dev/null
+++ b/src/test/e2e/api/admin/environment-oss.e2e.test.ts
@@ -0,0 +1,68 @@
+import {
+    type IUnleashTest,
+    setupAppWithCustomConfig,
+} from '../../helpers/test-helper';
+import dbInit, { type ITestDb } from '../../helpers/database-init';
+import getLogger from '../../../fixtures/no-logger';
+
+let app: IUnleashTest;
+let db: ITestDb;
+
+beforeAll(async () => {
+    db = await dbInit('environment_api_is_oss_serial', getLogger, {
+        isOss: true,
+    });
+    app = await setupAppWithCustomConfig(
+        db.stores,
+        {
+            experimental: {
+                flags: {
+                    strictSchemaValidation: true,
+                },
+            },
+            isOss: true,
+        },
+        db.rawDatabase,
+    );
+    await db.stores.environmentStore.create({
+        name: 'development',
+        type: 'development',
+        enabled: true,
+    });
+    await db.stores.environmentStore.create({
+        name: 'production',
+        type: 'production',
+        enabled: true,
+    });
+    await db.stores.environmentStore.create({
+        name: 'customenvironment',
+        type: 'production',
+        enabled: true,
+    });
+    await db.stores.environmentStore.create({
+        name: 'customenvironment2',
+        type: 'production',
+        enabled: true,
+    });
+    await db.stores.environmentStore.create({
+        name: 'customenvironment3',
+        type: 'production',
+        enabled: true,
+    });
+});
+
+afterAll(async () => {
+    await app.destroy();
+    await db.destroy();
+});
+
+test('querying environments in OSS only returns environments that are included in oss', async () => {
+    await app.request
+        .get('/api/admin/environments')
+        .expect(200)
+        .expect((res) => {
+            expect(res.body.environments).toHaveLength(3);
+            const names = res.body.environments.map((env) => env.name);
+            expect(names).toEqual(['default', 'development', 'production']);
+        });
+});
diff --git a/src/test/e2e/api/client/feature-oss.e2e.test.ts b/src/test/e2e/api/client/feature-oss.e2e.test.ts
new file mode 100644
index 000000000000..c54c8308980d
--- /dev/null
+++ b/src/test/e2e/api/client/feature-oss.e2e.test.ts
@@ -0,0 +1,82 @@
+import getLogger from '../../../fixtures/no-logger';
+import {
+    type IUnleashTest,
+    setupAppWithCustomConfig,
+} from '../../helpers/test-helper';
+import dbInit from '../../helpers/database-init';
+import type { ITestDb } from '../../helpers/database-init';
+import type { IAuditUser, IUser } from '../../../../lib/types';
+
+let app: IUnleashTest;
+let db: ITestDb;
+let testUser: IUser;
+const auditUser = {
+    username: 'audituser',
+    id: -42,
+    ip: 'localhost',
+} as IAuditUser;
+
+let userIndex = 0;
+const createUser = async (role?: number) => {
+    const name = `User ${userIndex}`;
+    const email = `user-${userIndex}@getunleash.io`;
+    userIndex++;
+
+    const { userStore } = db.stores;
+    return userStore.insert({ name, email });
+};
+
+beforeAll(async () => {
+    db = await dbInit('feature_api_client_is_oss', getLogger, { isOss: true });
+    app = await setupAppWithCustomConfig(
+        db.stores,
+        {
+            experimental: {
+                flags: {
+                    strictSchemaValidation: true,
+                },
+            },
+            isOss: true,
+        },
+        db.rawDatabase,
+    );
+    testUser = await createUser();
+    await app.services.projectService.createProject(
+        { id: 'secondproject', name: 'Second project not returned when oss' },
+        testUser,
+        auditUser,
+    );
+    await app.services.featureToggleService.createFeatureToggle(
+        'secondproject',
+        {
+            name: 'my.feature.toggle',
+        },
+        auditUser,
+        true,
+    );
+    await app.services.featureToggleService.createFeatureToggle(
+        'default',
+        {
+            name: 'my.default.toggle',
+        },
+        auditUser,
+        true,
+    );
+});
+afterAll(async () => {
+    await app.destroy();
+    await db.destroy();
+});
+
+describe('OSS downgrade', () => {
+    test('features created in projects other than default is not visible in client endpoint', async () => {
+        return app.request
+            .get('/api/client/features')
+            .expect('Content-Type', /json/)
+            .expect(200)
+            .expect((res) => {
+                expect(res.body.features).toHaveLength(1);
+                expect(res.body.features[0].name).toBe('my.default.toggle');
+            });
+    });
+});

From 1fdc542c7c83916451158eddbc67e5338a4c48a8 Mon Sep 17 00:00:00 2001
From: Christopher Kolstad <chriswk@getunleash.io>
Date: Fri, 29 Nov 2024 14:24:13 +0100
Subject: [PATCH 2/9] fix: added separate method for resolving isOss flag to be
 able to test behaviour when NODE_ENV !== test

---
 src/lib/create-config.test.ts | 21 ++++++++++++++++++++-
 src/lib/create-config.ts      | 21 ++++++++++++++++++---
 2 files changed, 38 insertions(+), 4 deletions(-)

diff --git a/src/lib/create-config.test.ts b/src/lib/create-config.test.ts
index e69eb7b500df..06646635e2a1 100644
--- a/src/lib/create-config.test.ts
+++ b/src/lib/create-config.test.ts
@@ -1,4 +1,4 @@
-import { createConfig } from './create-config';
+import { createConfig, resolveIsOss } from './create-config';
 import { ApiTokenType } from './types/models/api-token';
 
 test('should create default config', async () => {
@@ -498,3 +498,22 @@ test('Config with enterpriseVersion set and not pro environment should set isEnt
     });
     expect(config.isEnterprise).toBe(true);
 });
+
+describe('isOSS', () => {
+    test('Config with pro environment should set isOss to false regardless of pro casing', async () => {
+        const isOss = resolveIsOss(false, false, 'Pro');
+        expect(isOss).toBe(false);
+    });
+    test('Config with enterpriseVersion set should set isOss to false', async () => {
+        const isOss = resolveIsOss(true, false, 'Enterprise');
+        expect(isOss).toBe(false);
+    });
+    test('Config with no enterprise version and any other environment than pro should have isOss as true', async () => {
+        const isOss = resolveIsOss(false, false, 'my oss environment');
+        expect(isOss).toBe(true);
+    });
+    test('Config with enterprise false and isOss option set to false should return false in test mode', async () => {
+        const isOss = resolveIsOss(false, false, 'my environment', true);
+        expect(isOss).toBe(false);
+    });
+});
diff --git a/src/lib/create-config.ts b/src/lib/create-config.ts
index 015e7a9973ac..25194c47217a 100644
--- a/src/lib/create-config.ts
+++ b/src/lib/create-config.ts
@@ -496,6 +496,17 @@ const parseFrontendApiOrigins = (options: IUnleashOptions): string[] => {
     return frontendApiOrigins;
 };
 
+export function resolveIsOss(
+    isEnterprise: boolean,
+    isOssOption?: boolean,
+    uiEnvironment?: string,
+    testEnvironmentActive: boolean = false,
+): boolean {
+    return testEnvironmentActive
+        ? (isOssOption ?? false)
+        : !isEnterprise && uiEnvironment?.toLowerCase() !== 'pro';
+}
+
 export function createConfig(options: IUnleashOptions): IUnleashConfig {
     let extraDbOptions = {};
 
@@ -621,9 +632,12 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig {
         ui.environment?.toLowerCase() !== 'pro';
 
     const isTest = process.env.NODE_ENV === 'test';
-    const isOss = isTest
-        ? options.isOss || false
-        : !isEnterprise && ui.environment !== 'pro';
+    const isOss = resolveIsOss(
+        isEnterprise,
+        options.isOss,
+        ui.environment,
+        isTest,
+    );
     const metricsRateLimiting = loadMetricsRateLimitingConfig(options);
 
     const rateLimiting = loadRateLimitingConfig(options);
@@ -776,5 +790,6 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig {
 
 module.exports = {
     createConfig,
+    resolveIsOss,
     authTypeFromString,
 };

From 8e8bfa0e882f0cdec93b19e7e405791b29ca7c8d Mon Sep 17 00:00:00 2001
From: Christopher Kolstad <chriswk@getunleash.io>
Date: Fri, 29 Nov 2024 14:27:48 +0100
Subject: [PATCH 3/9] added one more test

---
 src/lib/create-config.test.ts | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/src/lib/create-config.test.ts b/src/lib/create-config.test.ts
index 06646635e2a1..940eedd1e37b 100644
--- a/src/lib/create-config.test.ts
+++ b/src/lib/create-config.test.ts
@@ -503,6 +503,10 @@ describe('isOSS', () => {
     test('Config with pro environment should set isOss to false regardless of pro casing', async () => {
         const isOss = resolveIsOss(false, false, 'Pro');
         expect(isOss).toBe(false);
+        const lowerCase = resolveIsOss(false, false, 'pro');
+        expect(lowerCase).toBe(false);
+        const strangeCase = resolveIsOss(false, false, 'PrO');
+        expect(strangeCase).toBe(false);
     });
     test('Config with enterpriseVersion set should set isOss to false', async () => {
         const isOss = resolveIsOss(true, false, 'Enterprise');
@@ -516,4 +520,14 @@ describe('isOSS', () => {
         const isOss = resolveIsOss(false, false, 'my environment', true);
         expect(isOss).toBe(false);
     });
+    test('Config with isOss option set to true should return true when test environment is active', async () => {
+        let isOss = resolveIsOss(false, true, 'Pro', true);
+        expect(isOss).toBe(true);
+
+        isOss = resolveIsOss(true, true, 'Pro', true);
+        expect(isOss).toBe(true);
+
+        isOss = resolveIsOss(false, true, 'some environment', true);
+        expect(isOss).toBe(true);
+    });
 });

From 59126e8cc3bb657fd4c2276768f542b905121fd1 Mon Sep 17 00:00:00 2001
From: Christopher Kolstad <chriswk@getunleash.io>
Date: Thu, 19 Dec 2024 10:58:18 +0100
Subject: [PATCH 4/9] make sure to not filter client features even if oss

---
 .../client-feature-toggle-store.ts                  |  4 ----
 .../features/feature-toggle/feature-toggle-store.ts | 10 ----------
 src/test/e2e/api/client/feature-oss.e2e.test.ts     | 13 -------------
 3 files changed, 27 deletions(-)

diff --git a/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts b/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts
index 400ff737d507..0a674fe5a688 100644
--- a/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts
+++ b/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts
@@ -109,10 +109,6 @@ export default class FeatureToggleClientStore
 
         let query = this.db('features')
             .modify(FeatureToggleStore.filterByArchived, archived)
-            .modify(
-                FeatureToggleStore.filterByProjectsAccessibleByOss,
-                this.isOss,
-            )
             .leftJoin(
                 this.db('feature_strategies')
                     .select('*')
diff --git a/src/lib/features/feature-toggle/feature-toggle-store.ts b/src/lib/features/feature-toggle/feature-toggle-store.ts
index 861d1af92c5e..fe412fb9986a 100644
--- a/src/lib/features/feature-toggle/feature-toggle-store.ts
+++ b/src/lib/features/feature-toggle/feature-toggle-store.ts
@@ -439,16 +439,6 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
             : queryBuilder.whereNull('archived_at');
     };
 
-    static filterByProjectsAccessibleByOss: Knex.QueryCallbackWithArgs = (
-        queryBuilder: Knex.QueryBuilder,
-        isOss: boolean,
-    ) => {
-        if (isOss) {
-            return queryBuilder.andWhere('project', '=', 'default');
-        }
-        return queryBuilder;
-    };
-
     rowToFeature(row: FeaturesTable): FeatureToggle {
         if (!row) {
             throw new NotFoundError('No feature flag found');
diff --git a/src/test/e2e/api/client/feature-oss.e2e.test.ts b/src/test/e2e/api/client/feature-oss.e2e.test.ts
index c54c8308980d..ce20b6495ff1 100644
--- a/src/test/e2e/api/client/feature-oss.e2e.test.ts
+++ b/src/test/e2e/api/client/feature-oss.e2e.test.ts
@@ -67,16 +67,3 @@ afterAll(async () => {
     await app.destroy();
     await db.destroy();
 });
-
-describe('OSS downgrade', () => {
-    test('features created in projects other than default is not visible in client endpoint', async () => {
-        return app.request
-            .get('/api/client/features')
-            .expect('Content-Type', /json/)
-            .expect(200)
-            .expect((res) => {
-                expect(res.body.features).toHaveLength(1);
-                expect(res.body.features[0].name).toBe('my.default.toggle');
-            });
-    });
-});

From 29a2cc06f8b2cb383873f6660a18f69a536fc3c8 Mon Sep 17 00:00:00 2001
From: Christopher Kolstad <chriswk@getunleash.io>
Date: Thu, 19 Dec 2024 11:03:59 +0100
Subject: [PATCH 5/9] Don't need isOss for FeatureToggleClientStore

---
 .../client-feature-toggles/client-feature-toggle-store.ts   | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts b/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts
index 0a674fe5a688..435dae0d74f7 100644
--- a/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts
+++ b/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts
@@ -47,16 +47,13 @@ export default class FeatureToggleClientStore
 
     private flagResolver: IFlagResolver;
 
-    private readonly isOss: boolean;
-
     constructor(
         db: Db,
         eventBus: EventEmitter,
         {
             getLogger,
             flagResolver,
-            isOss,
-        }: Pick<IUnleashConfig, 'getLogger' | 'flagResolver' | 'isOss'>,
+        }: Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>,
     ) {
         this.db = db;
         this.logger = getLogger('feature-toggle-client-store.ts');
@@ -66,7 +63,6 @@ export default class FeatureToggleClientStore
                 action,
             });
         this.flagResolver = flagResolver;
-        this.isOss = isOss;
     }
 
     private async getAll({

From de5f9bed658a88fae819e830b9b0c0a7fed9b91f Mon Sep 17 00:00:00 2001
From: Christopher Kolstad <chriswk@getunleash.io>
Date: Thu, 19 Dec 2024 11:46:21 +0100
Subject: [PATCH 6/9] Don't need isOss for FeatureToggleClientStore

---
 src/lib/db/index.ts                           |  6 +--
 .../onboarding/createOnboardingService.ts     |  6 +--
 .../createPersonalDashboardService.ts         |  2 +-
 .../project/createProjectReadModel.ts         |  9 ++---
 .../features/project/createProjectService.ts  |  6 +--
 .../features/project/project-read-model.ts    | 39 +++++++++++++------
 6 files changed, 35 insertions(+), 33 deletions(-)

diff --git a/src/lib/db/index.ts b/src/lib/db/index.ts
index 3722d91d46fd..7afc92d54939 100644
--- a/src/lib/db/index.ts
+++ b/src/lib/db/index.ts
@@ -178,11 +178,7 @@ export const createStores = (
         largestResourcesReadModel: new LargestResourcesReadModel(db),
         integrationEventsStore: new IntegrationEventsStore(db, { eventBus }),
         featureCollaboratorsReadModel: new FeatureCollaboratorsReadModel(db),
-        projectReadModel: createProjectReadModel(
-            db,
-            eventBus,
-            config.flagResolver,
-        ),
+        projectReadModel: createProjectReadModel(db, config),
         userUnsubscribeStore: new UserUnsubscribeStore(db),
         userSubscriptionsReadModel: new UserSubscriptionsReadModel(db),
     };
diff --git a/src/lib/features/onboarding/createOnboardingService.ts b/src/lib/features/onboarding/createOnboardingService.ts
index bd206182df5e..3e03fe71dc87 100644
--- a/src/lib/features/onboarding/createOnboardingService.ts
+++ b/src/lib/features/onboarding/createOnboardingService.ts
@@ -13,11 +13,7 @@ export const createOnboardingService =
     (db: Db): OnboardingService => {
         const { eventBus, flagResolver, getLogger } = config;
         const onboardingStore = new OnboardingStore(db);
-        const projectReadModel = new ProjectReadModel(
-            db,
-            eventBus,
-            flagResolver,
-        );
+        const projectReadModel = new ProjectReadModel(db, config);
         const userStore = new UserStore(db, getLogger, flagResolver);
         const onboardingService = new OnboardingService(
             {
diff --git a/src/lib/features/personal-dashboard/createPersonalDashboardService.ts b/src/lib/features/personal-dashboard/createPersonalDashboardService.ts
index 8f5955f970fd..dd66fab10359 100644
--- a/src/lib/features/personal-dashboard/createPersonalDashboardService.ts
+++ b/src/lib/features/personal-dashboard/createPersonalDashboardService.ts
@@ -27,7 +27,7 @@ export const createPersonalDashboardService = (
     return new PersonalDashboardService(
         new PersonalDashboardReadModel(db),
         new ProjectOwnersReadModel(db),
-        new ProjectReadModel(db, config.eventBus, config.flagResolver),
+        new ProjectReadModel(db, config),
         new OnboardingReadModel(db),
         new EventStore(db, config.getLogger),
         new FeatureEventFormatterMd({
diff --git a/src/lib/features/project/createProjectReadModel.ts b/src/lib/features/project/createProjectReadModel.ts
index 2a589475f6da..c4c10fa71830 100644
--- a/src/lib/features/project/createProjectReadModel.ts
+++ b/src/lib/features/project/createProjectReadModel.ts
@@ -1,16 +1,13 @@
-import type EventEmitter from 'events';
-import type { Db } from '../../server-impl';
+import type { Db, IUnleashConfig } from '../../server-impl';
 import type { IProjectReadModel } from './project-read-model-type';
-import type { IFlagResolver } from '../../types';
 import { ProjectReadModel } from './project-read-model';
 import { FakeProjectReadModel } from './fake-project-read-model';
 
 export const createProjectReadModel = (
     db: Db,
-    eventBus: EventEmitter,
-    flagResolver: IFlagResolver,
+    config: Pick<IUnleashConfig, 'eventBus' | 'flagResolver' | 'isOss'>,
 ): IProjectReadModel => {
-    return new ProjectReadModel(db, eventBus, flagResolver);
+    return new ProjectReadModel(db, config);
 };
 
 export const createFakeProjectReadModel = (): IProjectReadModel => {
diff --git a/src/lib/features/project/createProjectService.ts b/src/lib/features/project/createProjectService.ts
index a753ca34224f..bc69e0e59a15 100644
--- a/src/lib/features/project/createProjectService.ts
+++ b/src/lib/features/project/createProjectService.ts
@@ -123,11 +123,7 @@ export const createProjectService = (
         eventService,
     );
 
-    const projectReadModel = createProjectReadModel(
-        db,
-        eventBus,
-        config.flagResolver,
-    );
+    const projectReadModel = createProjectReadModel(db, config);
 
     const onboardingReadModel = createOnboardingReadModel(db);
 
diff --git a/src/lib/features/project/project-read-model.ts b/src/lib/features/project/project-read-model.ts
index 37190b8e068c..ef86bcb71eeb 100644
--- a/src/lib/features/project/project-read-model.ts
+++ b/src/lib/features/project/project-read-model.ts
@@ -1,4 +1,4 @@
-import type { IFlagResolver } from '../../types';
+import type { IFlagResolver, IUnleashConfig } from '../../types';
 import { Knex } from 'knex';
 import type { Db } from '../../db/db';
 import type {
@@ -8,7 +8,6 @@ import type {
 } from './project-read-model-type';
 import type { IProjectQuery, IProjectsQuery } from './project-store-type';
 import metricsHelper from '../../util/metrics-helper';
-import type EventEmitter from 'events';
 import type { IProjectMembersCount } from './project-store';
 import Raw = Knex.Raw;
 
@@ -52,7 +51,16 @@ export class ProjectReadModel implements IProjectReadModel {
 
     private flagResolver: IFlagResolver;
 
-    constructor(db: Db, eventBus: EventEmitter, flagResolver: IFlagResolver) {
+    private readonly isOss: boolean = false;
+
+    constructor(
+        db: Db,
+        {
+            eventBus,
+            flagResolver,
+            isOss,
+        }: Pick<IUnleashConfig, 'eventBus' | 'flagResolver' | 'isOss'>,
+    ) {
         this.db = db;
         this.timer = (action) =>
             metricsHelper.wrapTimer(eventBus, DB_TIME, {
@@ -60,6 +68,7 @@ export class ProjectReadModel implements IProjectReadModel {
                 action,
             });
         this.flagResolver = flagResolver;
+        this.isOss = isOss;
     }
 
     async getFeatureProject(
@@ -118,11 +127,15 @@ export class ProjectReadModel implements IProjectReadModel {
             projects = projects.where(`${TABLE}.archived_at`, null);
         }
 
-        if (query?.id) {
-            projects = projects.where(`${TABLE}.id`, query.id);
-        }
-        if (query?.ids) {
-            projects = projects.whereIn(`${TABLE}.id`, query.ids);
+        if (this.isOss) {
+            projects = projects.where(`${TABLE}.id`, 'default');
+        } else {
+            if (query?.id) {
+                projects = projects.where(`${TABLE}.id`, query.id);
+            }
+            if (query?.ids) {
+                projects = projects.whereIn(`${TABLE}.id`, query.ids);
+            }
         }
 
         let selectColumns = [
@@ -191,7 +204,9 @@ export class ProjectReadModel implements IProjectReadModel {
             projects = projects.where(`${TABLE}.archived_at`, null);
         }
 
-        if (query?.id) {
+        if (this.isOss) {
+            projects = projects.where(`${TABLE}.id`, 'default');
+        } else if (query?.id) {
             projects = projects.where(`${TABLE}.id`, query.id);
         }
 
@@ -294,14 +309,16 @@ export class ProjectReadModel implements IProjectReadModel {
     }
 
     async getProjectsFavoritedByUser(userId: number): Promise<string[]> {
-        const favoritedProjects = await this.db
+        const favoritedProjects = this.db
             .select('favorite_projects.project')
             .from('favorite_projects')
             .leftJoin('projects', 'favorite_projects.project', 'projects.id')
             .where('favorite_projects.user_id', userId)
             .andWhere('projects.archived_at', null)
             .pluck('project');
-
+        if (this.isOss) {
+            favoritedProjects.where('projects.id', 'default');
+        }
         return favoritedProjects;
     }
 }

From 3468161278b80f3972ee128f097347284d273b62 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gast=C3=B3n=20Fournier?= <gaston@getunleash.io>
Date: Thu, 19 Dec 2024 12:56:09 +0100
Subject: [PATCH 7/9] chore: restrict on RBAC middleware (#9000)

---
 src/lib/middleware/rbac-middleware.ts | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/src/lib/middleware/rbac-middleware.ts b/src/lib/middleware/rbac-middleware.ts
index 33ffd108b5a2..88e558f0e98e 100644
--- a/src/lib/middleware/rbac-middleware.ts
+++ b/src/lib/middleware/rbac-middleware.ts
@@ -33,7 +33,7 @@ export function findParam(
 }
 
 const rbacMiddleware = (
-    config: Pick<IUnleashConfig, 'getLogger'>,
+    config: Pick<IUnleashConfig, 'getLogger' | 'isOss'>,
     {
         featureToggleStore,
         segmentStore,
@@ -98,6 +98,24 @@ const rbacMiddleware = (
             ) {
                 projectId = 'default';
             }
+            if (config.isOss) {
+                if (projectId !== undefined && projectId !== 'default') {
+                    logger.error(
+                        'OSS is only allowed to work with default project.',
+                    );
+                    return false;
+                }
+                const ossEnvs = ['default', 'development', 'production'];
+                if (
+                    environment !== undefined &&
+                    !ossEnvs.includes(environment)
+                ) {
+                    logger.error(
+                        `OSS is only allowed to work with ${ossEnvs} environments.`,
+                    );
+                    return false;
+                }
+            }
 
             // DELETE segment does not include information about the segment's project
             // This is needed to check if the user has the right permissions on a project level

From e46d659949e51e411742da6fd964b1709d0da155 Mon Sep 17 00:00:00 2001
From: Christopher Kolstad <chriswk@getunleash.io>
Date: Thu, 19 Dec 2024 13:03:58 +0100
Subject: [PATCH 8/9] Revert changes to readModel

---
 src/lib/db/index.ts                           |  6 ++-
 .../onboarding/createOnboardingService.ts     |  6 ++-
 .../createPersonalDashboardService.ts         |  2 +-
 .../project/createProjectReadModel.ts         |  9 +++--
 .../features/project/createProjectService.ts  |  6 ++-
 .../features/project/project-read-model.ts    | 39 ++++++-------------
 6 files changed, 33 insertions(+), 35 deletions(-)

diff --git a/src/lib/db/index.ts b/src/lib/db/index.ts
index 7afc92d54939..3722d91d46fd 100644
--- a/src/lib/db/index.ts
+++ b/src/lib/db/index.ts
@@ -178,7 +178,11 @@ export const createStores = (
         largestResourcesReadModel: new LargestResourcesReadModel(db),
         integrationEventsStore: new IntegrationEventsStore(db, { eventBus }),
         featureCollaboratorsReadModel: new FeatureCollaboratorsReadModel(db),
-        projectReadModel: createProjectReadModel(db, config),
+        projectReadModel: createProjectReadModel(
+            db,
+            eventBus,
+            config.flagResolver,
+        ),
         userUnsubscribeStore: new UserUnsubscribeStore(db),
         userSubscriptionsReadModel: new UserSubscriptionsReadModel(db),
     };
diff --git a/src/lib/features/onboarding/createOnboardingService.ts b/src/lib/features/onboarding/createOnboardingService.ts
index 3e03fe71dc87..bd206182df5e 100644
--- a/src/lib/features/onboarding/createOnboardingService.ts
+++ b/src/lib/features/onboarding/createOnboardingService.ts
@@ -13,7 +13,11 @@ export const createOnboardingService =
     (db: Db): OnboardingService => {
         const { eventBus, flagResolver, getLogger } = config;
         const onboardingStore = new OnboardingStore(db);
-        const projectReadModel = new ProjectReadModel(db, config);
+        const projectReadModel = new ProjectReadModel(
+            db,
+            eventBus,
+            flagResolver,
+        );
         const userStore = new UserStore(db, getLogger, flagResolver);
         const onboardingService = new OnboardingService(
             {
diff --git a/src/lib/features/personal-dashboard/createPersonalDashboardService.ts b/src/lib/features/personal-dashboard/createPersonalDashboardService.ts
index dd66fab10359..8f5955f970fd 100644
--- a/src/lib/features/personal-dashboard/createPersonalDashboardService.ts
+++ b/src/lib/features/personal-dashboard/createPersonalDashboardService.ts
@@ -27,7 +27,7 @@ export const createPersonalDashboardService = (
     return new PersonalDashboardService(
         new PersonalDashboardReadModel(db),
         new ProjectOwnersReadModel(db),
-        new ProjectReadModel(db, config),
+        new ProjectReadModel(db, config.eventBus, config.flagResolver),
         new OnboardingReadModel(db),
         new EventStore(db, config.getLogger),
         new FeatureEventFormatterMd({
diff --git a/src/lib/features/project/createProjectReadModel.ts b/src/lib/features/project/createProjectReadModel.ts
index c4c10fa71830..2a589475f6da 100644
--- a/src/lib/features/project/createProjectReadModel.ts
+++ b/src/lib/features/project/createProjectReadModel.ts
@@ -1,13 +1,16 @@
-import type { Db, IUnleashConfig } from '../../server-impl';
+import type EventEmitter from 'events';
+import type { Db } from '../../server-impl';
 import type { IProjectReadModel } from './project-read-model-type';
+import type { IFlagResolver } from '../../types';
 import { ProjectReadModel } from './project-read-model';
 import { FakeProjectReadModel } from './fake-project-read-model';
 
 export const createProjectReadModel = (
     db: Db,
-    config: Pick<IUnleashConfig, 'eventBus' | 'flagResolver' | 'isOss'>,
+    eventBus: EventEmitter,
+    flagResolver: IFlagResolver,
 ): IProjectReadModel => {
-    return new ProjectReadModel(db, config);
+    return new ProjectReadModel(db, eventBus, flagResolver);
 };
 
 export const createFakeProjectReadModel = (): IProjectReadModel => {
diff --git a/src/lib/features/project/createProjectService.ts b/src/lib/features/project/createProjectService.ts
index bc69e0e59a15..a753ca34224f 100644
--- a/src/lib/features/project/createProjectService.ts
+++ b/src/lib/features/project/createProjectService.ts
@@ -123,7 +123,11 @@ export const createProjectService = (
         eventService,
     );
 
-    const projectReadModel = createProjectReadModel(db, config);
+    const projectReadModel = createProjectReadModel(
+        db,
+        eventBus,
+        config.flagResolver,
+    );
 
     const onboardingReadModel = createOnboardingReadModel(db);
 
diff --git a/src/lib/features/project/project-read-model.ts b/src/lib/features/project/project-read-model.ts
index ef86bcb71eeb..37190b8e068c 100644
--- a/src/lib/features/project/project-read-model.ts
+++ b/src/lib/features/project/project-read-model.ts
@@ -1,4 +1,4 @@
-import type { IFlagResolver, IUnleashConfig } from '../../types';
+import type { IFlagResolver } from '../../types';
 import { Knex } from 'knex';
 import type { Db } from '../../db/db';
 import type {
@@ -8,6 +8,7 @@ import type {
 } from './project-read-model-type';
 import type { IProjectQuery, IProjectsQuery } from './project-store-type';
 import metricsHelper from '../../util/metrics-helper';
+import type EventEmitter from 'events';
 import type { IProjectMembersCount } from './project-store';
 import Raw = Knex.Raw;
 
@@ -51,16 +52,7 @@ export class ProjectReadModel implements IProjectReadModel {
 
     private flagResolver: IFlagResolver;
 
-    private readonly isOss: boolean = false;
-
-    constructor(
-        db: Db,
-        {
-            eventBus,
-            flagResolver,
-            isOss,
-        }: Pick<IUnleashConfig, 'eventBus' | 'flagResolver' | 'isOss'>,
-    ) {
+    constructor(db: Db, eventBus: EventEmitter, flagResolver: IFlagResolver) {
         this.db = db;
         this.timer = (action) =>
             metricsHelper.wrapTimer(eventBus, DB_TIME, {
@@ -68,7 +60,6 @@ export class ProjectReadModel implements IProjectReadModel {
                 action,
             });
         this.flagResolver = flagResolver;
-        this.isOss = isOss;
     }
 
     async getFeatureProject(
@@ -127,15 +118,11 @@ export class ProjectReadModel implements IProjectReadModel {
             projects = projects.where(`${TABLE}.archived_at`, null);
         }
 
-        if (this.isOss) {
-            projects = projects.where(`${TABLE}.id`, 'default');
-        } else {
-            if (query?.id) {
-                projects = projects.where(`${TABLE}.id`, query.id);
-            }
-            if (query?.ids) {
-                projects = projects.whereIn(`${TABLE}.id`, query.ids);
-            }
+        if (query?.id) {
+            projects = projects.where(`${TABLE}.id`, query.id);
+        }
+        if (query?.ids) {
+            projects = projects.whereIn(`${TABLE}.id`, query.ids);
         }
 
         let selectColumns = [
@@ -204,9 +191,7 @@ export class ProjectReadModel implements IProjectReadModel {
             projects = projects.where(`${TABLE}.archived_at`, null);
         }
 
-        if (this.isOss) {
-            projects = projects.where(`${TABLE}.id`, 'default');
-        } else if (query?.id) {
+        if (query?.id) {
             projects = projects.where(`${TABLE}.id`, query.id);
         }
 
@@ -309,16 +294,14 @@ export class ProjectReadModel implements IProjectReadModel {
     }
 
     async getProjectsFavoritedByUser(userId: number): Promise<string[]> {
-        const favoritedProjects = this.db
+        const favoritedProjects = await this.db
             .select('favorite_projects.project')
             .from('favorite_projects')
             .leftJoin('projects', 'favorite_projects.project', 'projects.id')
             .where('favorite_projects.user_id', userId)
             .andWhere('projects.archived_at', null)
             .pluck('project');
-        if (this.isOss) {
-            favoritedProjects.where('projects.id', 'default');
-        }
+
         return favoritedProjects;
     }
 }

From 713f5317225bb52958de5453bb89c009c3df954f Mon Sep 17 00:00:00 2001
From: Christopher Kolstad <chriswk@getunleash.io>
Date: Thu, 19 Dec 2024 13:09:10 +0100
Subject: [PATCH 9/9] Remove non-running test

---
 .../e2e/api/client/feature-oss.e2e.test.ts    | 69 -------------------
 1 file changed, 69 deletions(-)
 delete mode 100644 src/test/e2e/api/client/feature-oss.e2e.test.ts

diff --git a/src/test/e2e/api/client/feature-oss.e2e.test.ts b/src/test/e2e/api/client/feature-oss.e2e.test.ts
deleted file mode 100644
index ce20b6495ff1..000000000000
--- a/src/test/e2e/api/client/feature-oss.e2e.test.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import getLogger from '../../../fixtures/no-logger';
-import {
-    type IUnleashTest,
-    setupAppWithCustomConfig,
-} from '../../helpers/test-helper';
-import dbInit from '../../helpers/database-init';
-import type { ITestDb } from '../../helpers/database-init';
-import type { IAuditUser, IUser } from '../../../../lib/types';
-
-let app: IUnleashTest;
-let db: ITestDb;
-let testUser: IUser;
-const auditUser = {
-    username: 'audituser',
-    id: -42,
-    ip: 'localhost',
-} as IAuditUser;
-
-let userIndex = 0;
-const createUser = async (role?: number) => {
-    const name = `User ${userIndex}`;
-    const email = `user-${userIndex}@getunleash.io`;
-    userIndex++;
-
-    const { userStore } = db.stores;
-    return userStore.insert({ name, email });
-};
-
-beforeAll(async () => {
-    db = await dbInit('feature_api_client_is_oss', getLogger, { isOss: true });
-    app = await setupAppWithCustomConfig(
-        db.stores,
-        {
-            experimental: {
-                flags: {
-                    strictSchemaValidation: true,
-                },
-            },
-            isOss: true,
-        },
-        db.rawDatabase,
-    );
-    testUser = await createUser();
-    await app.services.projectService.createProject(
-        { id: 'secondproject', name: 'Second project not returned when oss' },
-        testUser,
-        auditUser,
-    );
-    await app.services.featureToggleService.createFeatureToggle(
-        'secondproject',
-        {
-            name: 'my.feature.toggle',
-        },
-        auditUser,
-        true,
-    );
-    await app.services.featureToggleService.createFeatureToggle(
-        'default',
-        {
-            name: 'my.default.toggle',
-        },
-        auditUser,
-        true,
-    );
-});
-afterAll(async () => {
-    await app.destroy();
-    await db.destroy();
-});