Skip to content

Commit 8ae1f25

Browse files
authored
Merge pull request #41 from platform-mesh/feat/fix-the-kubeconfig-download-content
Fix the kubeconfig download content
2 parents 7a0d8f1 + 28251e1 commit 8ae1f25

19 files changed

+403
-159
lines changed

package-lock.json

Lines changed: 71 additions & 32 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
"@nestjs/core": ">=10.0.0",
3737
"@nestjs/platform-express": ">=10.0.0",
3838
"@nestjs/serve-static": ">=4.0.0",
39-
"@openmfp/portal-server-lib": ">=0.157.0",
39+
"@openmfp/portal-server-lib": ">=0.160.1",
4040
"axios": ">=1.7.7",
4141
"cookie-parser": ">=1.4.7",
4242
"express": ">=4.21.1",
@@ -48,7 +48,7 @@
4848
"@nestjs/axios": ">=3.0.0",
4949
"@nestjs/common": ">=10.0.0",
5050
"@nestjs/serve-static": ">=4.0.0",
51-
"@openmfp/portal-server-lib": ">=0.159.0",
51+
"@openmfp/portal-server-lib": ">=0.160.1",
5252
"axios": ">=1.7.7",
5353
"express": ">=4.21.1",
5454
"rxjs": ">=7.8.1"

src/portal-options/auth-config-provider.spec.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { PMAuthConfigProvider } from './auth-config-provider.js';
2+
import { getDomainAndOrganization } from './utils/domain.js';
23
import { HttpException } from '@nestjs/common';
34
import {
45
DiscoveryService,
@@ -53,6 +54,7 @@ describe('PMAuthConfigProvider', () => {
5354
oauthTokenUrl: 'token',
5455
clientId: 'cid',
5556
clientSecret: 'secret',
57+
oidcIssuerUrl: 'issuer',
5658
};
5759
envAuthConfigService.getAuthConfig.mockResolvedValue(expected);
5860

@@ -74,6 +76,7 @@ describe('PMAuthConfigProvider', () => {
7476
discoveryService.getOIDC.mockResolvedValue({
7577
authorization_endpoint: 'authUrl',
7678
token_endpoint: 'tokenUrl',
79+
issuer: 'issuer',
7780
});
7881

7982
const result = await provider.getAuthConfig(req);
@@ -98,7 +101,7 @@ describe('PMAuthConfigProvider', () => {
98101

99102
it('getDomain should return organization and baseDomain', () => {
100103
const req = { hostname: 'foo.example.com' } as Request;
101-
const result = provider.getDomain(req);
104+
const result = getDomainAndOrganization(req);
102105
expect(result).toEqual({
103106
organization: 'foo',
104107
baseDomain: 'example.com',
@@ -107,7 +110,7 @@ describe('PMAuthConfigProvider', () => {
107110

108111
it('getDomain should return clientId if hostname equals baseDomain', () => {
109112
const req = { hostname: 'example.com' } as Request;
110-
const result = provider.getDomain(req);
113+
const result = getDomainAndOrganization(req);
111114
expect(result).toEqual({
112115
organization: 'client123',
113116
baseDomain: 'example.com',

src/portal-options/auth-config-provider.ts

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -55,20 +55,11 @@ export class PMAuthConfigProvider implements AuthConfigService {
5555
return {
5656
idpName: clientId,
5757
baseDomain,
58-
oauthServerUrl,
5958
clientId,
6059
clientSecret,
60+
oauthServerUrl,
6161
oauthTokenUrl,
62-
};
63-
}
64-
65-
getDomain(request: Request): { organization?: string; baseDomain?: string } {
66-
const subDomain = request.hostname.split('.')[0];
67-
const clientId = process.env['OIDC_CLIENT_ID_DEFAULT'];
68-
const baseDomain = process.env['BASE_DOMAINS_DEFAULT'];
69-
return {
70-
organization: request.hostname === baseDomain ? clientId : subDomain,
71-
baseDomain,
62+
oidcIssuerUrl: oidc?.issuer,
7263
};
7364
}
7465

@@ -83,11 +74,10 @@ export class PMAuthConfigProvider implements AuthConfigService {
8374
});
8475
const secretData = res.data;
8576

86-
const clientSecret = Buffer.from(
77+
return Buffer.from(
8778
secretData['attribute.client_secret'],
8879
'base64',
8980
).toString('utf-8');
90-
return clientSecret;
9181
} catch (err) {
9282
console.error(
9383
`Failed to fetch secret ${secretName}:`,

src/portal-options/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
export * from './account-entity-context-provider.service.js';
2-
export * from './openmfp-portal-context.service.js';
3-
export * from './openmfp-request-context-provider.js';
2+
export * from './pm-portal-context.service.js';
3+
export * from './pm-request-context-provider.js';
44
export * from './auth-config-provider.js';
55
export * from './service-providers/content-configuration-service-providers.service.js';
66
export * from './service-providers/kubernetes-service-providers.service.js';
77
export * from './auth-callback-provider.js';
8+
9+
export * from './services/kcp-k8s.service.js';
810
export * from './services/iam-graphql.service.js';

src/portal-options/openmfp-portal-context.service.spec.ts renamed to src/portal-options/pm-portal-context.service.spec.ts

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { PMAuthConfigProvider } from './auth-config-provider.js';
2-
import { OpenmfpPortalContextService } from './openmfp-portal-context.service.js';
1+
import { PMPortalContextService } from './pm-portal-context.service.js';
2+
import { KcpKubernetesService } from './services/kcp-k8s.service.js';
3+
import { getDomainAndOrganization } from './utils/domain.js';
34
import { Test, TestingModule } from '@nestjs/testing';
45
import { Request } from 'express';
56
import { mock } from 'jest-mock-extended';
@@ -21,27 +22,32 @@ jest.mock('@kubernetes/client-node', () => {
2122
return { KubeConfig, CustomObjectsApi };
2223
});
2324

24-
describe('OpenmfpPortalContextService', () => {
25-
let service: OpenmfpPortalContextService;
26-
let pmAuthConfigProviderMock: jest.Mocked<PMAuthConfigProvider>;
25+
jest.mock('./utils/domain.js', () => ({
26+
getDomainAndOrganization: jest.fn(),
27+
}));
28+
29+
describe('PMPortalContextService', () => {
30+
let service: PMPortalContextService;
31+
let kcpKubernetesServiceMock: jest.Mocked<KcpKubernetesService>;
32+
const mockedGetDomainAndOrganization = jest.mocked(getDomainAndOrganization);
2733
let mockRequest: any;
2834

2935
beforeEach(async () => {
30-
pmAuthConfigProviderMock = mock();
36+
kcpKubernetesServiceMock = mock();
37+
38+
mockedGetDomainAndOrganization.mockReturnValue({
39+
baseDomain: 'example.com',
40+
organization: 'test-org',
41+
});
3142

3243
const module: TestingModule = await Test.createTestingModule({
3344
providers: [
34-
OpenmfpPortalContextService,
35-
{
36-
provide: PMAuthConfigProvider,
37-
useValue: pmAuthConfigProviderMock,
38-
},
45+
PMPortalContextService,
46+
{ provide: KcpKubernetesService, useValue: kcpKubernetesServiceMock },
3947
],
4048
}).compile();
4149

42-
service = module.get<OpenmfpPortalContextService>(
43-
OpenmfpPortalContextService,
44-
);
50+
service = module.get<PMPortalContextService>(PMPortalContextService);
4551
mockRequest = {
4652
hostname: 'test.example.com',
4753
};
@@ -57,12 +63,19 @@ describe('OpenmfpPortalContextService', () => {
5763
expect(service).toBeDefined();
5864
});
5965

60-
it('should return empty context when no environment variables match prefix', async () => {
61-
pmAuthConfigProviderMock.getDomain.mockReturnValue({
62-
baseDomain: 'example.com',
63-
organization: 'test-org',
66+
it('should return context with kcp workspace url', async () => {
67+
kcpKubernetesServiceMock.getKcpWorkspaceUrl.mockReturnValue(
68+
new URL('https://k8s.example.com/clusters/root:orgs:test-org'),
69+
);
70+
71+
const result = await service.getContextValues(mockRequest as Request);
72+
73+
expect(result).toEqual({
74+
kcpWorkspaceUrl: 'https://k8s.example.com/clusters/root:orgs:test-org',
6475
});
76+
});
6577

78+
it('should return empty context when no environment variables match prefix', async () => {
6679
const result = await service.getContextValues(mockRequest as Request);
6780

6881
expect(result).toEqual({});
@@ -74,7 +87,7 @@ describe('OpenmfpPortalContextService', () => {
7487
process.env.OTHER_ENV_VAR = 'should-be-ignored';
7588

7689
try {
77-
pmAuthConfigProviderMock.getDomain.mockReturnValue({
90+
mockedGetDomainAndOrganization.mockReturnValue({
7891
baseDomain: 'example.com',
7992
organization: 'test-org',
8093
});
@@ -97,7 +110,7 @@ describe('OpenmfpPortalContextService', () => {
97110
process.env.OPENMFP_PORTAL_CONTEXT_MULTIPLE_SNAKE_CASE_KEYS = 'value2';
98111

99112
try {
100-
pmAuthConfigProviderMock.getDomain.mockReturnValue({
113+
mockedGetDomainAndOrganization.mockReturnValue({
101114
baseDomain: 'example.com',
102115
organization: 'test-org',
103116
});
@@ -119,7 +132,7 @@ describe('OpenmfpPortalContextService', () => {
119132
'https://${org-subdomain}api.example.com/${org-name}/graphql';
120133

121134
try {
122-
pmAuthConfigProviderMock.getDomain.mockReturnValue({
135+
mockedGetDomainAndOrganization.mockReturnValue({
123136
baseDomain: 'example.com',
124137
organization: 'test-org',
125138
});
@@ -141,7 +154,7 @@ describe('OpenmfpPortalContextService', () => {
141154
'https://${org-subdomain}api.example.com/${org-name}/graphql';
142155

143156
try {
144-
pmAuthConfigProviderMock.getDomain.mockReturnValue({
157+
mockedGetDomainAndOrganization.mockReturnValue({
145158
baseDomain: 'example.com',
146159
organization: 'test-org',
147160
});
@@ -164,7 +177,7 @@ describe('OpenmfpPortalContextService', () => {
164177
process.env.OPENMFP_PORTAL_CONTEXT_VALID_KEY = 'valid-value';
165178

166179
try {
167-
pmAuthConfigProviderMock.getDomain.mockReturnValue({
180+
mockedGetDomainAndOrganization.mockReturnValue({
168181
baseDomain: 'example.com',
169182
organization: 'test-org',
170183
});
@@ -185,11 +198,6 @@ describe('OpenmfpPortalContextService', () => {
185198
process.env.OPENMFP_PORTAL_CONTEXT_OTHER_KEY = 'value';
186199

187200
try {
188-
pmAuthConfigProviderMock.getDomain.mockReturnValue({
189-
baseDomain: 'example.com',
190-
organization: 'test-org',
191-
});
192-
193201
const result = await service.getContextValues(mockRequest as Request);
194202

195203
expect(result).toEqual({

src/portal-options/openmfp-portal-context.service.ts renamed to src/portal-options/pm-portal-context.service.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
import { PMAuthConfigProvider } from './auth-config-provider.js';
1+
import { KcpKubernetesService } from './services/kcp-k8s.service.js';
2+
import { getDomainAndOrganization } from './utils/domain.js';
23
import { Injectable } from '@nestjs/common';
34
import { PortalContextProvider } from '@openmfp/portal-server-lib';
45
import type { Request } from 'express';
56
import process from 'node:process';
67

78
@Injectable()
8-
export class OpenmfpPortalContextService implements PortalContextProvider {
9+
export class PMPortalContextService implements PortalContextProvider {
910
private readonly openmfpPortalContext = 'OPENMFP_PORTAL_CONTEXT_';
1011

11-
constructor(private authConfigProvider: PMAuthConfigProvider) {}
12+
constructor(private kcpKubernetesService: KcpKubernetesService) {}
1213

1314
getContextValues(request: Request): Promise<Record<string, any>> {
1415
const portalContext: Record<string, any> = {};
@@ -25,14 +26,24 @@ export class OpenmfpPortalContextService implements PortalContextProvider {
2526
});
2627

2728
this.processGraphQLGatewayApiUrl(request, portalContext);
29+
this.addKcpWorkspaceUrl(request, portalContext);
2830
return Promise.resolve(portalContext);
2931
}
3032

33+
private addKcpWorkspaceUrl(request, portalContext) {
34+
const { organization } = getDomainAndOrganization(request);
35+
const account = request.query?.['core_platform-mesh_io_account'];
36+
37+
portalContext.kcpWorkspaceUrl = this.kcpKubernetesService
38+
.getKcpWorkspaceUrl(organization, account)
39+
?.toString();
40+
}
41+
3142
private processGraphQLGatewayApiUrl(
3243
request: Request,
3344
portalContext: Record<string, any>,
3445
): void {
35-
const org = this.authConfigProvider.getDomain(request);
46+
const org = getDomainAndOrganization(request);
3647
const subDomain =
3748
request.hostname === org.baseDomain ? '' : `${org.organization}.`;
3849
portalContext.crdGatewayApiUrl = portalContext.crdGatewayApiUrl

src/portal-options/openmfp-request-context-provider.spec.ts renamed to src/portal-options/pm-request-context-provider.spec.ts

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { PMAuthConfigProvider } from './auth-config-provider.js';
2-
import { OpenmfpPortalContextService } from './openmfp-portal-context.service.js';
3-
import { RequestContextProviderImpl } from './openmfp-request-context-provider.js';
1+
import { PMPortalContextService } from './pm-portal-context.service.js';
2+
import { PMRequestContextProvider } from './pm-request-context-provider.js';
3+
import { getDomainAndOrganization } from './utils/domain.js';
44
import type { Request } from 'express';
55
import { mock } from 'jest-mock-extended';
66

@@ -21,28 +21,29 @@ jest.mock('@kubernetes/client-node', () => {
2121
return { KubeConfig, CustomObjectsApi };
2222
});
2323

24-
describe('RequestContextProviderImpl', () => {
25-
let provider: RequestContextProviderImpl;
26-
const pmAuthConfigProviderMock = mock<PMAuthConfigProvider>();
27-
const portalContext = mock<OpenmfpPortalContextService>();
24+
jest.mock('./utils/domain.js', () => ({
25+
getDomainAndOrganization: jest.fn(),
26+
}));
27+
28+
describe('PMRequestContextProvider', () => {
29+
let provider: PMRequestContextProvider;
30+
const portalContextService = mock<PMPortalContextService>();
31+
const mockedGetDomainAndOrganization = jest.mocked(getDomainAndOrganization);
2832

2933
beforeEach(() => {
3034
jest.resetAllMocks();
31-
(
32-
pmAuthConfigProviderMock.getDomain as unknown as jest.Mock
33-
).mockReturnValue({
35+
mockedGetDomainAndOrganization.mockReturnValue({
3436
organization: 'org1',
3537
baseDomain: 'org1.example.com',
3638
});
37-
(portalContext.getContextValues as unknown as jest.Mock).mockResolvedValue({
39+
(
40+
portalContextService.getContextValues as unknown as jest.Mock
41+
).mockResolvedValue({
3842
crdGatewayApiUrl: 'http://gateway/graphql',
3943
other: 'x',
4044
});
4145

42-
provider = new RequestContextProviderImpl(
43-
pmAuthConfigProviderMock,
44-
portalContext,
45-
);
46+
provider = new PMRequestContextProvider(portalContextService);
4647
});
4748

4849
it('should merge request query, portal context and organization from envService', async () => {
@@ -61,7 +62,7 @@ describe('RequestContextProviderImpl', () => {
6162
organization: 'org1',
6263
});
6364

64-
expect(pmAuthConfigProviderMock.getDomain).toHaveBeenCalledWith(req);
65-
expect(portalContext.getContextValues).toHaveBeenCalledWith(req);
65+
expect(mockedGetDomainAndOrganization).toHaveBeenCalledWith(req);
66+
expect(portalContextService.getContextValues).toHaveBeenCalledWith(req);
6667
});
6768
});

src/portal-options/openmfp-request-context-provider.ts renamed to src/portal-options/pm-request-context-provider.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { PMAuthConfigProvider } from './auth-config-provider.js';
2-
import { OpenmfpPortalContextService } from './openmfp-portal-context.service.js';
1+
import { PMPortalContextService } from './pm-portal-context.service.js';
2+
import { getDomainAndOrganization } from './utils/domain.js';
33
import { Injectable } from '@nestjs/common';
44
import { RequestContextProvider } from '@openmfp/portal-server-lib';
55
import type { Request } from 'express';
@@ -12,17 +12,14 @@ export interface RequestContext extends Record<string, any> {
1212
}
1313

1414
@Injectable()
15-
export class RequestContextProviderImpl implements RequestContextProvider {
16-
constructor(
17-
private authConfigProvider: PMAuthConfigProvider,
18-
private openmfpPortalContextService: OpenmfpPortalContextService,
19-
) {}
15+
export class PMRequestContextProvider implements RequestContextProvider {
16+
constructor(private pmPortalContextService: PMPortalContextService) {}
2017

2118
async getContextValues(request: Request): Promise<RequestContext> {
22-
const domainData = this.authConfigProvider.getDomain(request);
19+
const domainData = getDomainAndOrganization(request);
2320
return {
2421
...request.query,
25-
...(await this.openmfpPortalContextService.getContextValues(request)),
22+
...(await this.pmPortalContextService.getContextValues(request)),
2623
organization: domainData.organization,
2724
isSubDomain: request.hostname !== domainData.baseDomain,
2825
};

src/portal-options/service-providers/content-configuration-service-providers.service.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { RequestContext } from '../openmfp-request-context-provider.js';
1+
import { RequestContext } from '../pm-request-context-provider.js';
22
import { ContentConfigurationServiceProvidersService } from './content-configuration-service-providers.service.js';
33
import { welcomeNodeConfig } from './models/welcome-node-config.js';
44
import { GraphQLClient } from 'graphql-request';

0 commit comments

Comments
 (0)