diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json
index 52291a20a7..06d42c4265 100644
--- a/apps/web/src/locales/en/messages.json
+++ b/apps/web/src/locales/en/messages.json
@@ -9002,6 +9002,24 @@
"purchasedSeatsRemoved": {
"message": "purchased seats removed"
},
+ "environmentVariables": {
+ "message": "Environment variables"
+ },
+ "organizationId": {
+ "message": "Organization ID"
+ },
+ "projectIds": {
+ "message": "Project IDs"
+ },
+ "projectId": {
+ "message": "Project ID"
+ },
+ "projectsAccessedByMachineAccount": {
+ "message": "The following projects can be accessed by this machine account."
+ },
+ "config": {
+ "message": "Config"
+ },
"learnMoreAboutEmergencyAccess": {
"message":"Learn more about emergency access"
},
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-list.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-list.view.ts
index 03a0353352..5f0aa9647c 100644
--- a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-list.view.ts
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-list.view.ts
@@ -6,4 +6,5 @@ export class ProjectListView {
revisionDate: string;
read: boolean;
write: boolean;
+ linkable: boolean;
}
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts
index 3b4add0c36..282980ece7 100644
--- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts
@@ -131,6 +131,7 @@ export class ProjectService {
);
projectListView.creationDate = s.creationDate;
projectListView.revisionDate = s.revisionDate;
+ projectListView.linkable = true;
return projectListView;
}),
);
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.html
new file mode 100644
index 0000000000..b17e47a39e
--- /dev/null
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.html
@@ -0,0 +1,47 @@
+
+
+
+
{{ "projectIds" | i18n }}
+
{{ "projectsNoItemsTitle" | i18n }}
+
{{ "projectsAccessedByMachineAccount" | i18n }}
+
+
+
+
+
+
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts
new file mode 100644
index 0000000000..47deeb2a41
--- /dev/null
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts
@@ -0,0 +1,127 @@
+import { Component, OnDestroy, OnInit } from "@angular/core";
+import { ActivatedRoute, Params } from "@angular/router";
+import { Subject, concatMap, takeUntil } from "rxjs";
+
+import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
+import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
+import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
+import { ToastService } from "@bitwarden/components";
+
+import { ProjectListView } from "../../models/view/project-list.view";
+import { ProjectService } from "../../projects/project.service";
+import { AccessPolicyService } from "../../shared/access-policies/access-policy.service";
+
+class ServiceAccountConfig {
+ organizationId: string;
+ serviceAccountId: string;
+ identityUrl: string;
+ apiUrl: string;
+ projects: ProjectListView[];
+}
+
+@Component({
+ selector: "sm-service-account-config",
+ templateUrl: "./config.component.html",
+})
+export class ServiceAccountConfigComponent implements OnInit, OnDestroy {
+ identityUrl: string;
+ apiUrl: string;
+ organizationId: string;
+ serviceAccountId: string;
+ projects: ProjectListView[];
+ hasProjects = false;
+
+ private destroy$ = new Subject();
+ loading = true;
+
+ constructor(
+ private environmentService: EnvironmentService,
+ private route: ActivatedRoute,
+ private platformUtilsService: PlatformUtilsService,
+ private toastService: ToastService,
+ private i18nService: I18nService,
+ private projectService: ProjectService,
+ private accessPolicyService: AccessPolicyService,
+ ) {}
+
+ async ngOnInit() {
+ this.route.params
+ .pipe(
+ concatMap(async (params: Params) => {
+ return await this.load(params.organizationId, params.serviceAccountId);
+ }),
+ takeUntil(this.destroy$),
+ )
+ .subscribe((smConfig) => {
+ this.identityUrl = smConfig.identityUrl;
+ this.apiUrl = smConfig.apiUrl;
+ this.organizationId = smConfig.organizationId;
+ this.serviceAccountId = smConfig.serviceAccountId;
+ this.projects = smConfig.projects;
+
+ this.hasProjects = smConfig.projects.length > 0;
+ this.loading = false;
+ });
+ }
+
+ async load(organizationId: string, serviceAccountId: string): Promise {
+ const environment = await this.environmentService.getEnvironment();
+
+ const allProjects = await this.projectService.getProjects(organizationId);
+ const policies = await this.accessPolicyService.getServiceAccountGrantedPolicies(
+ organizationId,
+ serviceAccountId,
+ );
+
+ const projects = policies.grantedProjectPolicies.map((policy) => {
+ return {
+ id: policy.accessPolicy.grantedProjectId,
+ name: policy.accessPolicy.grantedProjectName,
+ organizationId: organizationId,
+ linkable: allProjects.some(
+ (project) => project.id === policy.accessPolicy.grantedProjectId,
+ ),
+ } as ProjectListView;
+ });
+
+ return {
+ organizationId: organizationId,
+ serviceAccountId: serviceAccountId,
+ identityUrl: environment.getIdentityUrl(),
+ apiUrl: environment.getApiUrl(),
+ projects: projects,
+ } as ServiceAccountConfig;
+ }
+
+ copyIdentityUrl = () => {
+ this.platformUtilsService.copyToClipboard(this.identityUrl);
+ this.toastService.showToast({
+ variant: "success",
+ title: null,
+ message: this.i18nService.t("valueCopied", this.i18nService.t("identityUrl")),
+ });
+ };
+
+ copyApiUrl = () => {
+ this.platformUtilsService.copyToClipboard(this.apiUrl);
+ this.toastService.showToast({
+ variant: "success",
+ title: null,
+ message: this.i18nService.t("valueCopied", this.i18nService.t("apiUrl")),
+ });
+ };
+
+ copyOrganizationId = () => {
+ this.platformUtilsService.copyToClipboard(this.organizationId);
+ this.toastService.showToast({
+ variant: "success",
+ title: null,
+ message: this.i18nService.t("valueCopied", this.i18nService.t("organizationId")),
+ });
+ };
+
+ ngOnDestroy(): void {
+ this.destroy$.next();
+ this.destroy$.complete();
+ }
+}
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html
index c9ce8d8c64..392bfcb806 100644
--- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html
@@ -29,6 +29,7 @@
{{ "eventLogs" | i18n }}
+ {{ "config" | i18n }}