@platform-mesh/portal-server-lib 0.0.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/.github/workflows/pipeline.yaml +20 -0
  2. package/.prettierrc.mjs +6 -0
  3. package/CODEOWNERS +4 -0
  4. package/CODE_OF_CONDUCT.md +86 -0
  5. package/CONTRIBUTING.md +40 -0
  6. package/LICENSE +201 -0
  7. package/LICENSES/Apache-2.0.txt +73 -0
  8. package/README.md +41 -0
  9. package/base.jest.config.js +15 -0
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.js +2 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/portal-options/account-entity-context-provider.service.d.ts +4 -0
  14. package/dist/portal-options/account-entity-context-provider.service.js +35 -0
  15. package/dist/portal-options/account-entity-context-provider.service.js.map +1 -0
  16. package/dist/portal-options/auth-callback-provider.d.ts +10 -0
  17. package/dist/portal-options/auth-callback-provider.js +36 -0
  18. package/dist/portal-options/auth-callback-provider.js.map +1 -0
  19. package/dist/portal-options/auth-config-provider.d.ts +9 -0
  20. package/dist/portal-options/auth-config-provider.js +72 -0
  21. package/dist/portal-options/auth-config-provider.js.map +1 -0
  22. package/dist/portal-options/index.d.ts +10 -0
  23. package/dist/portal-options/index.js +11 -0
  24. package/dist/portal-options/index.js.map +1 -0
  25. package/dist/portal-options/logout-callback.service.d.ts +12 -0
  26. package/dist/portal-options/logout-callback.service.js +63 -0
  27. package/dist/portal-options/logout-callback.service.js.map +1 -0
  28. package/dist/portal-options/models/luigi-context.d.ts +4 -0
  29. package/dist/portal-options/models/luigi-context.js +2 -0
  30. package/dist/portal-options/models/luigi-context.js.map +1 -0
  31. package/dist/portal-options/pm-portal-context.service.d.ts +11 -0
  32. package/dist/portal-options/pm-portal-context.service.js +50 -0
  33. package/dist/portal-options/pm-portal-context.service.js.map +1 -0
  34. package/dist/portal-options/pm-request-context-provider.d.ts +13 -0
  35. package/dist/portal-options/pm-request-context-provider.js +34 -0
  36. package/dist/portal-options/pm-request-context-provider.js.map +1 -0
  37. package/dist/portal-options/service-providers/content-configuration-service-providers.service.d.ts +5 -0
  38. package/dist/portal-options/service-providers/content-configuration-service-providers.service.js +83 -0
  39. package/dist/portal-options/service-providers/content-configuration-service-providers.service.js.map +1 -0
  40. package/dist/portal-options/service-providers/contentconfigurations-query.d.ts +1 -0
  41. package/dist/portal-options/service-providers/contentconfigurations-query.js +22 -0
  42. package/dist/portal-options/service-providers/contentconfigurations-query.js.map +1 -0
  43. package/dist/portal-options/service-providers/kubernetes-service-providers.service.d.ts +8 -0
  44. package/dist/portal-options/service-providers/kubernetes-service-providers.service.js +98 -0
  45. package/dist/portal-options/service-providers/kubernetes-service-providers.service.js.map +1 -0
  46. package/dist/portal-options/service-providers/models/contentconfigurations.d.ts +20 -0
  47. package/dist/portal-options/service-providers/models/contentconfigurations.js +2 -0
  48. package/dist/portal-options/service-providers/models/contentconfigurations.js.map +1 -0
  49. package/dist/portal-options/service-providers/models/welcome-node-config.d.ts +2 -0
  50. package/dist/portal-options/service-providers/models/welcome-node-config.js +35 -0
  51. package/dist/portal-options/service-providers/models/welcome-node-config.js.map +1 -0
  52. package/dist/portal-options/services/iam-graphql.service.d.ts +7 -0
  53. package/dist/portal-options/services/iam-graphql.service.js +40 -0
  54. package/dist/portal-options/services/iam-graphql.service.js.map +1 -0
  55. package/dist/portal-options/services/kcp-k8s.service.d.ts +11 -0
  56. package/dist/portal-options/services/kcp-k8s.service.js +60 -0
  57. package/dist/portal-options/services/kcp-k8s.service.js.map +1 -0
  58. package/dist/portal-options/services/queries.d.ts +1 -0
  59. package/dist/portal-options/services/queries.js +7 -0
  60. package/dist/portal-options/services/queries.js.map +1 -0
  61. package/dist/portal-options/utils/domain.d.ts +3 -0
  62. package/dist/portal-options/utils/domain.js +11 -0
  63. package/dist/portal-options/utils/domain.js.map +1 -0
  64. package/eslint.config.mjs +27 -0
  65. package/jest.config.ts +42 -0
  66. package/nest-cli.json +6 -0
  67. package/package.json +85 -2
  68. package/renovate.json +6 -0
  69. package/src/index.ts +1 -0
  70. package/src/portal-options/account-entity-context-provider.service.ts +30 -0
  71. package/src/portal-options/auth-callback-provider.spec.ts +85 -0
  72. package/src/portal-options/auth-callback-provider.ts +27 -0
  73. package/src/portal-options/auth-config-provider.spec.ts +101 -0
  74. package/src/portal-options/auth-config-provider.ts +82 -0
  75. package/src/portal-options/index.ts +11 -0
  76. package/src/portal-options/logout-callback.service.spec.ts +113 -0
  77. package/src/portal-options/logout-callback.service.ts +60 -0
  78. package/src/portal-options/models/luigi-context.ts +4 -0
  79. package/src/portal-options/pm-portal-context.service.spec.ts +155 -0
  80. package/src/portal-options/pm-portal-context.service.ts +63 -0
  81. package/src/portal-options/pm-request-context-provider.spec.ts +69 -0
  82. package/src/portal-options/pm-request-context-provider.ts +33 -0
  83. package/src/portal-options/service-providers/content-configuration-service-providers.service.spec.ts +157 -0
  84. package/src/portal-options/service-providers/content-configuration-service-providers.service.ts +130 -0
  85. package/src/portal-options/service-providers/contentconfigurations-query.ts +22 -0
  86. package/src/portal-options/service-providers/kubernetes-service-providers.service.spec.ts +197 -0
  87. package/src/portal-options/service-providers/kubernetes-service-providers.service.ts +115 -0
  88. package/src/portal-options/service-providers/models/contentconfigurations.ts +13 -0
  89. package/src/portal-options/service-providers/models/welcome-node-config.ts +36 -0
  90. package/src/portal-options/services/iam-graphql.service.spec.ts +77 -0
  91. package/src/portal-options/services/iam-graphql.service.ts +33 -0
  92. package/src/portal-options/services/kcp-k8s.service.spec.ts +78 -0
  93. package/src/portal-options/services/kcp-k8s.service.ts +56 -0
  94. package/src/portal-options/services/queries.ts +7 -0
  95. package/src/portal-options/utils/domain.spec.ts +114 -0
  96. package/src/portal-options/utils/domain.ts +13 -0
  97. package/tsconfig.build.json +10 -0
  98. package/tsconfig.json +18 -0
  99. package/tsconfig.test.json +3 -0
@@ -0,0 +1,35 @@
1
+ export const welcomeNodeConfig = {
2
+ rawServiceProviders: [
3
+ {
4
+ name: 'platform-mesh-system',
5
+ displayName: '',
6
+ creationTimestamp: '',
7
+ contentConfiguration: [
8
+ {
9
+ name: 'platform-mesh-system',
10
+ creationTimestamp: '',
11
+ luigiConfigFragment: {
12
+ data: {
13
+ nodes: [
14
+ {
15
+ entityType: 'global',
16
+ pathSegment: 'welcome',
17
+ hideFromNav: true,
18
+ hideSideNav: true,
19
+ showBreadcrumbs: false,
20
+ order: 1,
21
+ url: '/assets/platform-mesh-portal-ui-wc.js#welcome-view',
22
+ webcomponent: {
23
+ selfRegistered: true,
24
+ },
25
+ context: { kcpPath: 'root:orgs' },
26
+ },
27
+ ],
28
+ },
29
+ },
30
+ },
31
+ ],
32
+ },
33
+ ],
34
+ };
35
+ //# sourceMappingURL=welcome-node-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"welcome-node-config.js","sourceRoot":"","sources":["../../../../src/portal-options/service-providers/models/welcome-node-config.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,iBAAiB,GAA4B;IACxD,mBAAmB,EAAE;QACnB;YACE,IAAI,EAAE,sBAAsB;YAC5B,WAAW,EAAE,EAAE;YACf,iBAAiB,EAAE,EAAE;YACrB,oBAAoB,EAAE;gBACpB;oBACE,IAAI,EAAE,sBAAsB;oBAC5B,iBAAiB,EAAE,EAAE;oBACrB,mBAAmB,EAAE;wBACnB,IAAI,EAAE;4BACJ,KAAK,EAAE;gCACL;oCACE,UAAU,EAAE,QAAQ;oCACpB,WAAW,EAAE,SAAS;oCACtB,WAAW,EAAE,IAAI;oCACjB,WAAW,EAAE,IAAI;oCACjB,eAAe,EAAE,KAAK;oCACtB,KAAK,EAAE,CAAC;oCACR,GAAG,EAAE,oDAAoD;oCACzD,YAAY,EAAE;wCACZ,cAAc,EAAE,IAAI;qCACrB;oCACD,OAAO,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE;iCAClC;6BACF;yBACF;qBACF;iBACF;aACF;SACF;KACF;CACF,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { PMRequestContextProvider } from '../pm-request-context-provider.js';
2
+ import type { Request, Response } from 'express';
3
+ export declare class IAMGraphQlService {
4
+ private requestContextProvider;
5
+ constructor(requestContextProvider: PMRequestContextProvider);
6
+ addUser(token: string, request: Request, response: Response): Promise<void>;
7
+ }
@@ -0,0 +1,40 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { PMRequestContextProvider } from '../pm-request-context-provider.js';
11
+ import { MUTATION_LOGIN } from './queries.js';
12
+ import { Injectable } from '@nestjs/common';
13
+ import { GraphQLClient } from 'graphql-request';
14
+ let IAMGraphQlService = class IAMGraphQlService {
15
+ requestContextProvider;
16
+ constructor(requestContextProvider) {
17
+ this.requestContextProvider = requestContextProvider;
18
+ }
19
+ async addUser(token, request, response) {
20
+ const requestContext = await this.requestContextProvider.getContextValues(request, response);
21
+ const iamUrl = requestContext.iamServiceApiUrl;
22
+ const client = new GraphQLClient(iamUrl, {
23
+ headers: {
24
+ Authorization: `Bearer ${token}`,
25
+ },
26
+ });
27
+ try {
28
+ await client.request(MUTATION_LOGIN);
29
+ }
30
+ catch (e) {
31
+ console.error(e);
32
+ }
33
+ }
34
+ };
35
+ IAMGraphQlService = __decorate([
36
+ Injectable(),
37
+ __metadata("design:paramtypes", [PMRequestContextProvider])
38
+ ], IAMGraphQlService);
39
+ export { IAMGraphQlService };
40
+ //# sourceMappingURL=iam-graphql.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iam-graphql.service.js","sourceRoot":"","sources":["../../../src/portal-options/services/iam-graphql.service.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGzC,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IACR;IAApB,YAAoB,sBAAgD;QAAhD,2BAAsB,GAAtB,sBAAsB,CAA0B;IAAG,CAAC;IAExE,KAAK,CAAC,OAAO,CACX,KAAa,EACb,OAAgB,EAChB,QAAkB;QAElB,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,sBAAsB,CAAC,gBAAgB,CACvE,OAAO,EACP,QAAQ,CACT,CAAC;QACF,MAAM,MAAM,GAAG,cAAc,CAAC,gBAAgB,CAAC;QAC/C,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,MAAM,EAAE;YACvC,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,KAAK,EAAE;aACjC;SACF,CAAC,CAAC;QAEH,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QACvC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;CACF,CAAA;AAzBY,iBAAiB;IAD7B,UAAU,EAAE;qCAEiC,wBAAwB;GADzD,iBAAiB,CAyB7B"}
@@ -0,0 +1,11 @@
1
+ import { CustomObjectsApi } from '@kubernetes/client-node';
2
+ export declare class KcpKubernetesService {
3
+ private readonly k8sApi;
4
+ private readonly baseUrl;
5
+ constructor();
6
+ getKcpK8sApiClient(): CustomObjectsApi;
7
+ private buildWorkspacePath;
8
+ getKcpVirtualWorkspaceUrl(organization: string, account: string): URL;
9
+ getKcpWorkspaceUrl(organization: string, account: string): URL;
10
+ getKcpWorkspacePublicUrl(organization: string, account: string): string;
11
+ }
@@ -0,0 +1,60 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { CustomObjectsApi, KubeConfig } from '@kubernetes/client-node';
11
+ import { Injectable } from '@nestjs/common';
12
+ let KcpKubernetesService = class KcpKubernetesService {
13
+ k8sApi;
14
+ baseUrl;
15
+ constructor() {
16
+ const kubeConfigKcp = process.env['KUBECONFIG_KCP'];
17
+ const kc = new KubeConfig();
18
+ kc.loadFromFile(kubeConfigKcp);
19
+ kc.addUser({
20
+ name: 'oidc',
21
+ });
22
+ kc.addContext({
23
+ name: 'oidc',
24
+ user: 'oidc',
25
+ cluster: kc.getCurrentCluster()?.name || '',
26
+ });
27
+ kc.setCurrentContext('oidc');
28
+ this.baseUrl = new URL(kc.getCurrentCluster()?.server || '');
29
+ this.k8sApi = kc.makeApiClient(CustomObjectsApi);
30
+ }
31
+ getKcpK8sApiClient() {
32
+ return this.k8sApi;
33
+ }
34
+ buildWorkspacePath(organization, account) {
35
+ let path = `root:orgs:${organization}`;
36
+ if (account) {
37
+ path += `:${account}`;
38
+ }
39
+ return path;
40
+ }
41
+ getKcpVirtualWorkspaceUrl(organization, account) {
42
+ const path = this.buildWorkspacePath(organization, account);
43
+ return new URL(`${this.baseUrl.origin}/services/contentconfigurations/clusters/${path}`);
44
+ }
45
+ getKcpWorkspaceUrl(organization, account) {
46
+ const path = this.buildWorkspacePath(organization, account);
47
+ return new URL(`${this.baseUrl.origin}/clusters/${path}`);
48
+ }
49
+ getKcpWorkspacePublicUrl(organization, account) {
50
+ const path = this.buildWorkspacePath(organization, account);
51
+ const baseDomain = process.env['BASE_DOMAINS_DEFAULT'];
52
+ return `https://kcp.api.${baseDomain}/clusters/${path}`;
53
+ }
54
+ };
55
+ KcpKubernetesService = __decorate([
56
+ Injectable(),
57
+ __metadata("design:paramtypes", [])
58
+ ], KcpKubernetesService);
59
+ export { KcpKubernetesService };
60
+ //# sourceMappingURL=kcp-k8s.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kcp-k8s.service.js","sourceRoot":"","sources":["../../../src/portal-options/services/kcp-k8s.service.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGrC,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IACd,MAAM,CAAmB;IACzB,OAAO,CAAM;IAE9B;QACE,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QACpD,MAAM,EAAE,GAAG,IAAI,UAAU,EAAE,CAAC;QAC5B,EAAE,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC;QAE/B,EAAE,CAAC,OAAO,CAAC;YACT,IAAI,EAAE,MAAM;SACb,CAAC,CAAC;QACH,EAAE,CAAC,UAAU,CAAC;YACZ,IAAI,EAAE,MAAM;YACZ,IAAI,EAAE,MAAM;YACZ,OAAO,EAAE,EAAE,CAAC,iBAAiB,EAAE,EAAE,IAAI,IAAI,EAAE;SAC5C,CAAC,CAAC;QACH,EAAE,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC,iBAAiB,EAAE,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC;QAC7D,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC;IACnD,CAAC;IAED,kBAAkB;QAChB,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAEO,kBAAkB,CAAC,YAAoB,EAAE,OAAgB;QAC/D,IAAI,IAAI,GAAG,aAAa,YAAY,EAAE,CAAC;QACvC,IAAI,OAAO,EAAE,CAAC;YACZ,IAAI,IAAI,IAAI,OAAO,EAAE,CAAC;QACxB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,yBAAyB,CAAC,YAAoB,EAAE,OAAe;QAC7D,MAAM,IAAI,GAAG,IAAI,CAAC,kBAAkB,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QAC5D,OAAO,IAAI,GAAG,CACZ,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,4CAA4C,IAAI,EAAE,CACzE,CAAC;IACJ,CAAC;IAED,kBAAkB,CAAC,YAAoB,EAAE,OAAe;QACtD,MAAM,IAAI,GAAG,IAAI,CAAC,kBAAkB,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QAC5D,OAAO,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,aAAa,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,wBAAwB,CAAC,YAAoB,EAAE,OAAe;QAC5D,MAAM,IAAI,GAAG,IAAI,CAAC,kBAAkB,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QAC5D,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;QACvD,OAAO,mBAAmB,UAAU,aAAa,IAAI,EAAE,CAAC;IAC1D,CAAC;CACF,CAAA;AAnDY,oBAAoB;IADhC,UAAU,EAAE;;GACA,oBAAoB,CAmDhC"}
@@ -0,0 +1 @@
1
+ export declare const MUTATION_LOGIN: string;
@@ -0,0 +1,7 @@
1
+ import { gql } from 'graphql-request';
2
+ export const MUTATION_LOGIN = gql `
3
+ mutation {
4
+ login
5
+ }
6
+ `;
7
+ //# sourceMappingURL=queries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.js","sourceRoot":"","sources":["../../../src/portal-options/services/queries.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAEtC,MAAM,CAAC,MAAM,cAAc,GAAG,GAAG,CAAA;;;;CAIhC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Request } from 'express';
2
+ export declare const getOrganization: (request: Request) => string;
3
+ export declare const getDiscoveryEndpoint: (request: Request) => string;
@@ -0,0 +1,11 @@
1
+ export const getOrganization = (request) => {
2
+ const subDomain = request.hostname.split('.')[0];
3
+ const clientId = process.env['OIDC_CLIENT_ID_DEFAULT'];
4
+ const baseDomain = process.env['BASE_DOMAINS_DEFAULT'];
5
+ return request.hostname !== baseDomain ? subDomain : clientId;
6
+ };
7
+ export const getDiscoveryEndpoint = (request) => {
8
+ const clientId = getOrganization(request);
9
+ return process.env[`DISCOVERY_ENDPOINT`]?.replace('${org-name}', clientId);
10
+ };
11
+ //# sourceMappingURL=domain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain.js","sourceRoot":"","sources":["../../../src/portal-options/utils/domain.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,OAAgB,EAAU,EAAE;IAC1D,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IACvD,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;IACvD,OAAO,OAAO,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;AAChE,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,OAAgB,EAAU,EAAE;IAC/D,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IAC1C,OAAO,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;AAC7E,CAAC,CAAC"}
@@ -0,0 +1,27 @@
1
+ // @ts-check
2
+ import tsPlugin from 'typescript-eslint';
3
+
4
+ export default tsPlugin.config(
5
+ ...tsPlugin.configs.recommended,
6
+ {
7
+ ignores: ['dist'],
8
+ },
9
+ {
10
+ languageOptions: {
11
+ parserOptions: {
12
+ projectService: true,
13
+ tsconfigRootDir: import.meta.dirname,
14
+ },
15
+ },
16
+ },
17
+ {
18
+ files: ['**/*.ts'],
19
+ rules: {
20
+ '@typescript-eslint/no-explicit-any': 'off',
21
+ },
22
+ },
23
+ {
24
+ files: ['**/*.{js,mjs,cjs}'],
25
+ extends: [tsPlugin.configs.disableTypeChecked],
26
+ },
27
+ );
package/jest.config.ts ADDED
@@ -0,0 +1,42 @@
1
+ /* eslint-disable @typescript-eslint/no-require-imports */
2
+ import baseConfig from './base.jest.config.js';
3
+
4
+ export default {
5
+ ...baseConfig,
6
+ rootDir: 'src',
7
+ testRegex: '.spec.ts$',
8
+ collectCoverage: true,
9
+ reporters: ['default'],
10
+ coverageThreshold: {
11
+ global: {
12
+ branches: 75,
13
+ functions: 90,
14
+ lines: 90,
15
+ statements: -12,
16
+ },
17
+ },
18
+ coveragePathIgnorePatterns: ['/node_modules/', '/integration-tests/'],
19
+ coverageDirectory: '../test-run-reports/coverage/unit',
20
+ transformIgnorePatterns: [
21
+ '/node_modules/(?!(@openmfp/portal-server-lib|graphql-request)/)',
22
+ ],
23
+ transform: {
24
+ '^.+\\.(t|j)s$': [
25
+ 'ts-jest',
26
+ {
27
+ tsconfig: 'tsconfig.test.json',
28
+ useESM: true,
29
+ },
30
+ ],
31
+ },
32
+ testEnvironment: 'node',
33
+ passWithNoTests: true,
34
+ roots: ['<rootDir>'],
35
+ moduleNameMapper: {
36
+ '^@openmfp/portal-lib(|/.*)$': '<rootDir>/libs/portal-lib/src/$1',
37
+ '^(\\.{1,2}/.*)\\.js$': '$1',
38
+ },
39
+ preset: 'ts-jest/presets/default-esm',
40
+ extensionsToTreatAsEsm: ['.ts'],
41
+ moduleFileExtensions: ['js', 'json', 'ts'],
42
+ };
package/nest-cli.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "type": "library",
3
+ "root": ".",
4
+ "collection": "@nestjs/schematics",
5
+ "sourceRoot": "src"
6
+ }
package/package.json CHANGED
@@ -1,4 +1,87 @@
1
1
  {
2
2
  "name": "@platform-mesh/portal-server-lib",
3
- "version": "0.0.0"
4
- }
3
+ "version": "0.5.2",
4
+ "author": "Platform Mesh",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "url": "git+https://github.com/platform-mesh/portal-server-lib.git"
8
+ },
9
+ "description": "This library helps you to set up the backend application using @openmfp/portal-server-lib by providing the set of the required implementations \nin the scope of the Platform Mesh functionalities.",
10
+ "main": "dist/index.js",
11
+ "types": "dist/index.d.ts",
12
+ "exports": {
13
+ "./portal-options": {
14
+ "import": "./dist/portal-options/index.js",
15
+ "types": "./dist/portal-options/index.d.ts"
16
+ },
17
+ "./services": {
18
+ "import": "./dist/services/index.js",
19
+ "types": "./dist/services/index.d.ts"
20
+ },
21
+ ".": {
22
+ "import": "./dist/index.js",
23
+ "types": "./dist/index.d.ts"
24
+ }
25
+ },
26
+ "scripts": {
27
+ "build": "nest build",
28
+ "build:watch": "mkdirp dist && nodemon --ignore dist --ext js,yml,yaml,ts,html,css,scss,json,md --exec \"rimraf dist && npm run build && yalc publish --push --sig\"",
29
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"libs/**/*.ts\"",
30
+ "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
31
+ "test": "jest",
32
+ "test:watch": "jest --watch",
33
+ "test:cov": "jest --coverage",
34
+ "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand"
35
+ },
36
+ "peerDependencies": {
37
+ "@nestjs/axios": ">=3.0.0",
38
+ "@nestjs/common": ">=10.0.0",
39
+ "@nestjs/core": ">=10.0.0",
40
+ "@nestjs/platform-express": ">=10.0.0",
41
+ "@nestjs/serve-static": ">=4.0.0",
42
+ "@openmfp/portal-server-lib": "0.161.0",
43
+ "axios": ">=1.7.7",
44
+ "cookie-parser": "1.4.7",
45
+ "express": "5.1.0",
46
+ "rxjs": ">=7.8.1",
47
+ "graphql-request": "7.3.1",
48
+ "@kubernetes/client-node": "1.4.0"
49
+ },
50
+ "dependencies": {
51
+ "@nestjs/axios": ">=3.0.0",
52
+ "@nestjs/common": ">=10.0.0",
53
+ "@nestjs/serve-static": ">=4.0.0",
54
+ "@openmfp/portal-server-lib": "0.162.8",
55
+ "axios": ">=1.7.7",
56
+ "express": "5.1.0",
57
+ "rxjs": ">=7.8.1"
58
+ },
59
+ "devDependencies": {
60
+ "@eslint/js": "9.38.0",
61
+ "@nestjs/cli": "^11.0.0",
62
+ "@nestjs/testing": "^11.0.0",
63
+ "@openmfp/config-prettier": "0.9.1",
64
+ "@types/jest": "30.0.0",
65
+ "@types/node": "24.9.2",
66
+ "@types/supertest": "6.0.3",
67
+ "eslint": "9.38.0",
68
+ "eslint-config-prettier": "10.1.8",
69
+ "globals": "16.4.0",
70
+ "jest": "30.1.3",
71
+ "jest-junit": "16.0.0",
72
+ "jest-mock-extended": "4.0.0",
73
+ "mkdirp": "3.0.1",
74
+ "nock": "14.0.10",
75
+ "nodemon": "3.1.10",
76
+ "prettier": "3.6.2",
77
+ "rimraf": "6.0.1",
78
+ "supertest": "7.1.4",
79
+ "ts-jest": "29.4.4",
80
+ "ts-loader": "9.5.4",
81
+ "ts-node": "10.9.2",
82
+ "tsconfig-paths": "4.2.0",
83
+ "typescript": "5.9.2",
84
+ "typescript-eslint": "^8.0.0"
85
+ },
86
+ "type": "module"
87
+ }
package/renovate.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "extends": [
4
+ "local>platform-mesh/.github:renovate-config"
5
+ ]
6
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export const EMPTY = true;
@@ -0,0 +1,30 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { EntityContextProvider } from '@openmfp/portal-server-lib';
3
+
4
+ @Injectable()
5
+ export class AccountEntityContextProvider implements EntityContextProvider {
6
+ async getContextValues(
7
+ _token: string,
8
+ context?: Record<string, any>,
9
+ ): Promise<Record<string, any>> {
10
+ return {
11
+ id: context['core_platform-mesh_io_account'],
12
+ policies: [
13
+ 'create',
14
+ 'delete',
15
+ 'get',
16
+ 'list',
17
+ 'update',
18
+ 'watch',
19
+ 'gardener_project_create',
20
+ 'gardener_project_list',
21
+ 'gardener_shoot_create',
22
+ 'gardener_shoot_list',
23
+ 'iamAdmin',
24
+ 'projectAdmin',
25
+ 'projectMember',
26
+ 'providerAdmin',
27
+ ],
28
+ };
29
+ }
30
+ }
@@ -0,0 +1,85 @@
1
+ import { AuthCallbackProvider } from './auth-callback-provider.js';
2
+ import { IAMGraphQlService } from './services/iam-graphql.service.js';
3
+ import { Test, TestingModule } from '@nestjs/testing';
4
+ import type { AuthTokenData } from '@openmfp/portal-server-lib';
5
+ import type { Request, Response } from 'express';
6
+ import { mock } from 'jest-mock-extended';
7
+
8
+ jest.mock('@kubernetes/client-node', () => {
9
+ class KubeConfig {
10
+ loadFromDefault = jest.fn();
11
+ loadFromFile = jest.fn();
12
+ getCurrentCluster = jest.fn().mockReturnValue({
13
+ server: 'https://k8s.example.com/base',
14
+ name: 'test-cluster',
15
+ });
16
+ makeApiClient = jest.fn();
17
+ addUser = jest.fn();
18
+ addContext = jest.fn();
19
+ setCurrentContext = jest.fn();
20
+ }
21
+ class CustomObjectsApi {}
22
+ return { KubeConfig, CustomObjectsApi };
23
+ });
24
+
25
+ describe('AuthCallbackProvider', () => {
26
+ let callback: AuthCallbackProvider;
27
+ let iamServiceMock: IAMGraphQlService;
28
+
29
+ beforeEach(async () => {
30
+ iamServiceMock = mock<IAMGraphQlService>();
31
+ const module: TestingModule = await Test.createTestingModule({
32
+ providers: [
33
+ AuthCallbackProvider,
34
+ {
35
+ provide: IAMGraphQlService,
36
+ useValue: iamServiceMock,
37
+ },
38
+ ],
39
+ }).compile();
40
+
41
+ callback = module.get<AuthCallbackProvider>(AuthCallbackProvider);
42
+ });
43
+
44
+ it('should be defined', () => {
45
+ expect(callback).toBeDefined();
46
+ });
47
+
48
+ it('should create a user', async () => {
49
+ const req = mock<Request>();
50
+ const res = mock<Response>();
51
+
52
+ await callback.handleSuccess(req, res, {
53
+ id_token: 'idtoken',
54
+ } as AuthTokenData);
55
+
56
+ expect(iamServiceMock.addUser).toHaveBeenCalledTimes(1);
57
+ expect(iamServiceMock.addUser).toHaveBeenCalledWith('idtoken', req, res);
58
+ });
59
+
60
+ it('should log error if addUser throws', async () => {
61
+ const req = mock<Request>();
62
+ const res = mock<Response>();
63
+ const error = new Error('boom');
64
+ (iamServiceMock.addUser as jest.Mock).mockRejectedValueOnce(error);
65
+
66
+ const errorSpy = jest
67
+
68
+ .spyOn((callback as any).logger, 'error')
69
+ .mockImplementation(() => undefined as unknown as never);
70
+
71
+ await callback.handleSuccess(req, res, {
72
+ id_token: 'bad',
73
+ } as AuthTokenData);
74
+
75
+ expect(iamServiceMock.addUser).toHaveBeenCalledTimes(1);
76
+ expect(errorSpy).toHaveBeenCalledWith(error);
77
+ });
78
+
79
+ it('should resolve handleFailure without action', async () => {
80
+ const req = mock<Request>();
81
+ const res = mock<Response>();
82
+
83
+ await expect(callback.handleFailure(req, res)).resolves.toBeUndefined();
84
+ });
85
+ });
@@ -0,0 +1,27 @@
1
+ import { IAMGraphQlService } from './services/iam-graphql.service.js';
2
+ import { Injectable, Logger } from '@nestjs/common';
3
+ import { AuthCallback, AuthTokenData } from '@openmfp/portal-server-lib';
4
+ import { Request, Response } from 'express';
5
+
6
+ @Injectable()
7
+ export class AuthCallbackProvider implements AuthCallback {
8
+ private logger: Logger = new Logger(AuthCallbackProvider.name);
9
+
10
+ constructor(private iamService: IAMGraphQlService) {}
11
+
12
+ async handleSuccess(
13
+ request: Request,
14
+ response: Response,
15
+ authTokenResponse: AuthTokenData,
16
+ ): Promise<void> {
17
+ try {
18
+ await this.iamService.addUser(authTokenResponse.id_token, request, response);
19
+ } catch (e) {
20
+ this.logger.error(e);
21
+ }
22
+ }
23
+
24
+ async handleFailure(request: Request, response: Response): Promise<void> {
25
+ return Promise.resolve();
26
+ }
27
+ }
@@ -0,0 +1,101 @@
1
+ import { PMAuthConfigProvider } from './auth-config-provider.js';
2
+ import { HttpException } from '@nestjs/common';
3
+ import {
4
+ DiscoveryService,
5
+ EnvAuthConfigService,
6
+ } from '@openmfp/portal-server-lib';
7
+ import type { Request } from 'express';
8
+ import { mock } from 'jest-mock-extended';
9
+
10
+ jest.mock('@kubernetes/client-node', () => {
11
+ class KubeConfig {
12
+ loadFromDefault = jest.fn();
13
+ loadFromFile = jest.fn();
14
+ getCurrentCluster = jest.fn().mockReturnValue({
15
+ server: 'https://k8s.example.com/base',
16
+ name: 'test-cluster',
17
+ });
18
+ makeApiClient = jest.fn();
19
+ addUser = jest.fn();
20
+ addContext = jest.fn();
21
+ setCurrentContext = jest.fn();
22
+ }
23
+ class CustomObjectsApi {}
24
+ return { KubeConfig, CustomObjectsApi };
25
+ });
26
+
27
+ describe('PMAuthConfigProvider', () => {
28
+ let provider: PMAuthConfigProvider;
29
+ let discoveryService: jest.Mocked<DiscoveryService>;
30
+ let envAuthConfigService: jest.Mocked<EnvAuthConfigService>;
31
+
32
+ beforeEach(() => {
33
+ discoveryService = mock<DiscoveryService>();
34
+ envAuthConfigService = mock<EnvAuthConfigService>();
35
+ provider = new PMAuthConfigProvider(discoveryService);
36
+ jest.resetModules();
37
+ process.env = {
38
+ AUTH_SERVER_URL_DEFAULT: 'authUrl',
39
+ TOKEN_URL_DEFAULT: 'tokenUrl',
40
+ BASE_DOMAINS_DEFAULT: 'example.com',
41
+ OIDC_CLIENT_ID_DEFAULT: 'client123',
42
+ OIDC_CLIENT_SECRET_DEFAULT: 'secret123',
43
+ };
44
+ provider['getClientSecret'] = jest.fn().mockResolvedValue('secret');
45
+ });
46
+
47
+ it('should delegate to EnvAuthConfigService if available', async () => {
48
+ const req = { hostname: 'foo.example.com' } as Request;
49
+ const expected = {
50
+ idpName: 'idp',
51
+ baseDomain: 'example.com',
52
+ oauthServerUrl: 'url',
53
+ oauthTokenUrl: 'token',
54
+ clientId: 'cid',
55
+ clientSecret: 'secret',
56
+ oidcIssuerUrl: 'issuer',
57
+ };
58
+ envAuthConfigService.getAuthConfig.mockResolvedValue(expected);
59
+
60
+ const result = await provider.getAuthConfig(req);
61
+
62
+ expect(result).toEqual({
63
+ baseDomain: 'example.com',
64
+ clientId: 'foo',
65
+ clientSecret: 'secret',
66
+ idpName: 'foo',
67
+ oauthServerUrl: 'authUrl',
68
+ oauthTokenUrl: 'tokenUrl',
69
+ });
70
+ });
71
+
72
+ it('should fall back to default configuration if EnvAuthConfigService throws', async () => {
73
+ const req = { hostname: 'foo.example.com' } as Request;
74
+ envAuthConfigService.getAuthConfig.mockRejectedValue(new Error('fail'));
75
+ discoveryService.getOIDC.mockResolvedValue({
76
+ authorization_endpoint: 'authUrl',
77
+ token_endpoint: 'tokenUrl',
78
+ issuer: 'issuer',
79
+ end_session_endpoint: 'endSessionUrl',
80
+ });
81
+
82
+ const result = await provider.getAuthConfig(req);
83
+
84
+ expect(result).toMatchObject({
85
+ baseDomain: 'example.com',
86
+ oauthServerUrl: 'authUrl',
87
+ oauthTokenUrl: 'tokenUrl',
88
+ clientId: 'foo',
89
+ clientSecret: 'secret',
90
+ });
91
+ });
92
+
93
+ it('should throw if default configuration incomplete', async () => {
94
+ const req = { hostname: 'foo.example.com' } as Request;
95
+ envAuthConfigService.getAuthConfig.mockRejectedValue(new Error('fail'));
96
+ discoveryService.getOIDC.mockResolvedValue(null);
97
+ process.env = {};
98
+
99
+ await expect(provider.getAuthConfig(req)).rejects.toThrow(HttpException);
100
+ });
101
+ });