Skip to content
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"@nestjs/core": ">=10.0.0",
"@nestjs/platform-express": ">=10.0.0",
"@nestjs/serve-static": ">=4.0.0",
"@openmfp/portal-server-lib": ">=0.160.1",
"@openmfp/portal-server-lib": ">=0.160.2",
"axios": ">=1.7.7",
"cookie-parser": ">=1.4.7",
"express": ">=4.21.1",
Expand All @@ -48,7 +48,7 @@
"@nestjs/axios": ">=3.0.0",
"@nestjs/common": ">=10.0.0",
"@nestjs/serve-static": ">=4.0.0",
"@openmfp/portal-server-lib": ">=0.160.1",
"@openmfp/portal-server-lib": ">=0.160.2",
"axios": ">=1.7.7",
"express": ">=4.21.1",
"rxjs": ">=7.8.1"
Expand Down
19 changes: 0 additions & 19 deletions src/portal-options/auth-config-provider.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { PMAuthConfigProvider } from './auth-config-provider.js';
import { getDomainAndOrganization } from './utils/domain.js';
import { HttpException } from '@nestjs/common';
import {
DiscoveryService,
Expand Down Expand Up @@ -98,22 +97,4 @@ describe('PMAuthConfigProvider', () => {

await expect(provider.getAuthConfig(req)).rejects.toThrow(HttpException);
});

it('getDomain should return organization and baseDomain', () => {
const req = { hostname: 'foo.example.com' } as Request;
const result = getDomainAndOrganization(req);
expect(result).toEqual({
organization: 'foo',
baseDomain: 'example.com',
});
});

it('getDomain should return clientId if hostname equals baseDomain', () => {
const req = { hostname: 'example.com' } as Request;
const result = getDomainAndOrganization(req);
expect(result).toEqual({
organization: 'client123',
baseDomain: 'example.com',
});
});
});
9 changes: 2 additions & 7 deletions src/portal-options/auth-config-provider.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getOrganization } from './utils/domain.js';
import { CoreV1Api, KubeConfig } from '@kubernetes/client-node';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import {
Expand All @@ -19,13 +20,7 @@ export class PMAuthConfigProvider implements AuthConfigService {

async getAuthConfig(request: Request): Promise<ServerAuthVariables> {
const baseDomain = process.env['BASE_DOMAINS_DEFAULT'];

const subDomain = request.hostname.split('.')[0];
const isSubdomain = request.hostname !== baseDomain;

const clientId = isSubdomain
? subDomain
: process.env['OIDC_CLIENT_ID_DEFAULT'];
const clientId = getOrganization(request);
const clientSecret = await this.getClientSecret(clientId);

const oidcUrl = process.env[`DISCOVERY_ENDPOINT`]?.replace(
Expand Down
39 changes: 11 additions & 28 deletions src/portal-options/pm-portal-context.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { PMPortalContextService } from './pm-portal-context.service.js';
import { KcpKubernetesService } from './services/kcp-k8s.service.js';
import { getDomainAndOrganization } from './utils/domain.js';
import { getOrganization } from './utils/domain.js';
import { Test, TestingModule } from '@nestjs/testing';
import { Request } from 'express';
import { mock } from 'jest-mock-extended';
import process from 'node:process';

jest.mock('@kubernetes/client-node', () => {
class KubeConfig {
Expand All @@ -23,22 +24,19 @@ jest.mock('@kubernetes/client-node', () => {
});

jest.mock('./utils/domain.js', () => ({
getDomainAndOrganization: jest.fn(),
getOrganization: jest.fn(),
}));

describe('PMPortalContextService', () => {
let service: PMPortalContextService;
let kcpKubernetesServiceMock: jest.Mocked<KcpKubernetesService>;
const mockedGetDomainAndOrganization = jest.mocked(getDomainAndOrganization);
const mockedGetDomainAndOrganization = jest.mocked(getOrganization);
let mockRequest: any;

beforeEach(async () => {
kcpKubernetesServiceMock = mock();

mockedGetDomainAndOrganization.mockReturnValue({
baseDomain: 'example.com',
organization: 'test-org',
});
mockedGetDomainAndOrganization.mockReturnValue('test-org');

const module: TestingModule = await Test.createTestingModule({
providers: [
Expand Down Expand Up @@ -87,10 +85,7 @@ describe('PMPortalContextService', () => {
process.env.OTHER_ENV_VAR = 'should-be-ignored';

try {
mockedGetDomainAndOrganization.mockReturnValue({
baseDomain: 'example.com',
organization: 'test-org',
});
mockedGetDomainAndOrganization.mockReturnValue('test-org');

const result = await service.getContextValues(mockRequest as Request);

Expand All @@ -110,10 +105,7 @@ describe('PMPortalContextService', () => {
process.env.OPENMFP_PORTAL_CONTEXT_MULTIPLE_SNAKE_CASE_KEYS = 'value2';

try {
mockedGetDomainAndOrganization.mockReturnValue({
baseDomain: 'example.com',
organization: 'test-org',
});
mockedGetDomainAndOrganization.mockReturnValue('test-org');

const result = await service.getContextValues(mockRequest as Request);

Expand All @@ -132,10 +124,7 @@ describe('PMPortalContextService', () => {
'https://${org-subdomain}api.example.com/${org-name}/graphql';

try {
mockedGetDomainAndOrganization.mockReturnValue({
baseDomain: 'example.com',
organization: 'test-org',
});
mockedGetDomainAndOrganization.mockReturnValue('test-org');

mockRequest.hostname = 'subdomain.example.com';

Expand All @@ -154,11 +143,8 @@ describe('PMPortalContextService', () => {
'https://${org-subdomain}api.example.com/${org-name}/graphql';

try {
mockedGetDomainAndOrganization.mockReturnValue({
baseDomain: 'example.com',
organization: 'test-org',
});

mockedGetDomainAndOrganization.mockReturnValue('test-org');
process.env['BASE_DOMAINS_DEFAULT'] = 'example.com';
mockRequest.hostname = 'example.com';

const result = await service.getContextValues(mockRequest as Request);
Expand All @@ -177,10 +163,7 @@ describe('PMPortalContextService', () => {
process.env.OPENMFP_PORTAL_CONTEXT_VALID_KEY = 'valid-value';

try {
mockedGetDomainAndOrganization.mockReturnValue({
baseDomain: 'example.com',
organization: 'test-org',
});
mockedGetDomainAndOrganization.mockReturnValue('test-org');

const result = await service.getContextValues(mockRequest as Request);

Expand Down
12 changes: 6 additions & 6 deletions src/portal-options/pm-portal-context.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { KcpKubernetesService } from './services/kcp-k8s.service.js';
import { getDomainAndOrganization } from './utils/domain.js';
import { getOrganization } from './utils/domain.js';
import { Injectable } from '@nestjs/common';
import { PortalContextProvider } from '@openmfp/portal-server-lib';
import type { Request } from 'express';
Expand Down Expand Up @@ -31,7 +31,7 @@ export class PMPortalContextService implements PortalContextProvider {
}

private addKcpWorkspaceUrl(request, portalContext) {
const { organization } = getDomainAndOrganization(request);
const organization = getOrganization(request);
const account = request.query?.['core_platform-mesh_io_account'];

portalContext.kcpWorkspaceUrl = this.kcpKubernetesService
Expand All @@ -43,12 +43,12 @@ export class PMPortalContextService implements PortalContextProvider {
request: Request,
portalContext: Record<string, any>,
): void {
const org = getDomainAndOrganization(request);
const subDomain =
request.hostname === org.baseDomain ? '' : `${org.organization}.`;
const org = getOrganization(request);
const baseDomain = process.env['BASE_DOMAINS_DEFAULT'];
const subDomain = request.hostname !== baseDomain ? `${org}.` : '';
portalContext.crdGatewayApiUrl = portalContext.crdGatewayApiUrl
?.replace('${org-subdomain}', subDomain)
.replace('${org-name}', org.organization);
.replace('${org-name}', org);
}

private toCamelCase(text: string): string {
Expand Down
13 changes: 5 additions & 8 deletions src/portal-options/pm-request-context-provider.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { PMPortalContextService } from './pm-portal-context.service.js';
import { PMRequestContextProvider } from './pm-request-context-provider.js';
import { getDomainAndOrganization } from './utils/domain.js';
import { getOrganization } from './utils/domain.js';
import type { Request } from 'express';
import { mock } from 'jest-mock-extended';

Expand All @@ -22,20 +22,17 @@ jest.mock('@kubernetes/client-node', () => {
});

jest.mock('./utils/domain.js', () => ({
getDomainAndOrganization: jest.fn(),
getOrganization: jest.fn(),
}));

describe('PMRequestContextProvider', () => {
let provider: PMRequestContextProvider;
const portalContextService = mock<PMPortalContextService>();
const mockedGetDomainAndOrganization = jest.mocked(getDomainAndOrganization);
const mockedGetOrganization = jest.mocked(getOrganization);

beforeEach(() => {
jest.resetAllMocks();
mockedGetDomainAndOrganization.mockReturnValue({
organization: 'org1',
baseDomain: 'org1.example.com',
});
mockedGetOrganization.mockReturnValue('org1');
(
portalContextService.getContextValues as unknown as jest.Mock
).mockResolvedValue({
Expand All @@ -62,7 +59,7 @@ describe('PMRequestContextProvider', () => {
organization: 'org1',
});

expect(mockedGetDomainAndOrganization).toHaveBeenCalledWith(req);
expect(mockedGetOrganization).toHaveBeenCalledWith(req);
expect(portalContextService.getContextValues).toHaveBeenCalledWith(req);
});
});
9 changes: 5 additions & 4 deletions src/portal-options/pm-request-context-provider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PMPortalContextService } from './pm-portal-context.service.js';
import { getDomainAndOrganization } from './utils/domain.js';
import { getOrganization } from './utils/domain.js';
import { Injectable } from '@nestjs/common';
import { RequestContextProvider } from '@openmfp/portal-server-lib';
import type { Request } from 'express';
Expand All @@ -16,12 +16,13 @@ export class PMRequestContextProvider implements RequestContextProvider {
constructor(private pmPortalContextService: PMPortalContextService) {}

async getContextValues(request: Request): Promise<RequestContext> {
const domainData = getDomainAndOrganization(request);
const organization = getOrganization(request);
const baseDomain = process.env['BASE_DOMAINS_DEFAULT'];
return {
...request.query,
...(await this.pmPortalContextService.getContextValues(request)),
organization: domainData.organization,
isSubDomain: request.hostname !== domainData.baseDomain,
organization,
isSubDomain: request.hostname !== baseDomain,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ describe('ContentConfigurationServiceProvidersService', () => {
expect(result).toEqual(welcomeNodeConfig);
});

it('throws if context organization is missing', async () => {
context.isSubDomain = false;
const result = await service.getServiceProviders(
'token',
['entity'],
context,
);

expect(result).toEqual(welcomeNodeConfig);
});

it('returns parsed content configurations', async () => {
mockClient.request.mockResolvedValue({
ui_platform_mesh_io: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { RequestContext } from '../pm-request-context-provider.js';
import { contentConfigurationsQuery } from './contentconfigurations-query.js';
import { ContentConfigurationQueryResponse } from './models/contentconfigurations.js';
import { welcomeNodeConfig } from './models/welcome-node-config.js';
import { Injectable } from '@nestjs/common';
import {
ContentConfiguration,
ServiceProviderResponse,
ServiceProviderService,
} from '@openmfp/portal-server-lib';
import { GraphQLClient } from 'graphql-request';

@Injectable()
export class ContentConfigurationServiceProvidersService
implements ServiceProviderService
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const welcomeNodeConfig: ServiceProviderResponse = {
pathSegment: 'welcome',
hideFromNav: true,
hideSideNav: true,
showBreadcrumbs: false,
order: 1,
url: '/assets/platform-mesh-portal-ui-wc.js#welcome-view',
webcomponent: {
Expand Down
Loading