mirror of
https://github.com/bitwarden/browser
synced 2026-02-14 15:33:55 +00:00
resolve merge conflicts
This commit is contained in:
@@ -50,6 +50,6 @@
|
||||
</bit-form-control>
|
||||
<bit-form-control>
|
||||
<input type="checkbox" bitCheckbox formControlName="requireSpecial" id="requireSpecial" />
|
||||
<bit-label>!@#$%^&*</bit-label>
|
||||
<bit-label>!@#$%^&*</bit-label>
|
||||
</bit-form-control>
|
||||
</div>
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
</bit-form-control>
|
||||
<bit-form-control>
|
||||
<input type="checkbox" bitCheckbox formControlName="useSpecial" id="useSpecial" />
|
||||
<bit-label>!@#$%^&*</bit-label>
|
||||
<bit-label>!@#$%^&*</bit-label>
|
||||
</bit-form-control>
|
||||
<h3 bitTypography="h3" class="tw-mt-4">{{ "passphrase" | i18n }}</h3>
|
||||
<div class="tw-grid tw-grid-cols-12 tw-gap-4">
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./webauthn-login";
|
||||
export * from "./set-password-jit";
|
||||
export * from "./registration";
|
||||
|
||||
@@ -145,6 +145,7 @@ describe("DefaultRegistrationFinishService", () => {
|
||||
passwordInputResult = {
|
||||
masterKey: masterKey,
|
||||
masterKeyHash: "masterKeyHash",
|
||||
localMasterKeyHash: "localMasterKeyHash",
|
||||
kdfConfig: DEFAULT_KDF_CONFIG,
|
||||
hint: "hint",
|
||||
};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./web-set-password-jit.service";
|
||||
@@ -0,0 +1,27 @@
|
||||
import { inject } from "@angular/core";
|
||||
|
||||
import {
|
||||
DefaultSetPasswordJitService,
|
||||
SetPasswordCredentials,
|
||||
SetPasswordJitService,
|
||||
} from "@bitwarden/auth/angular";
|
||||
|
||||
import { RouterService } from "../../../../core/router.service";
|
||||
import { AcceptOrganizationInviteService } from "../../../organization-invite/accept-organization.service";
|
||||
|
||||
export class WebSetPasswordJitService
|
||||
extends DefaultSetPasswordJitService
|
||||
implements SetPasswordJitService
|
||||
{
|
||||
routerService = inject(RouterService);
|
||||
acceptOrganizationInviteService = inject(AcceptOrganizationInviteService);
|
||||
|
||||
override async setPassword(credentials: SetPasswordCredentials) {
|
||||
await super.setPassword(credentials);
|
||||
|
||||
// SSO JIT accepts org invites when setting their MP, meaning
|
||||
// we can clear the deep linked url for accepting it.
|
||||
await this.routerService.getAndClearLoginRedirectUrl();
|
||||
await this.acceptOrganizationInviteService.clearOrganizationInvitation();
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,12 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
import { DisableTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/disable-two-factor-authenticator.request";
|
||||
import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/update-two-factor-authenticator.request";
|
||||
import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response";
|
||||
import { AuthResponse } from "@bitwarden/common/auth/types/auth-response";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@@ -43,6 +46,7 @@ export class TwoFactorAuthenticatorComponent
|
||||
@Output() onChangeStatus = new EventEmitter<boolean>();
|
||||
type = TwoFactorProviderType.Authenticator;
|
||||
key: string;
|
||||
private userVerificationToken: string;
|
||||
|
||||
override componentName = "app-two-factor-authenticator";
|
||||
qrScriptError = false;
|
||||
@@ -63,6 +67,7 @@ export class TwoFactorAuthenticatorComponent
|
||||
logService: LogService,
|
||||
private accountService: AccountService,
|
||||
dialogService: DialogService,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
super(
|
||||
apiService,
|
||||
@@ -112,16 +117,46 @@ export class TwoFactorAuthenticatorComponent
|
||||
const request = await this.buildRequestModel(UpdateTwoFactorAuthenticatorRequest);
|
||||
request.token = this.formGroup.value.token;
|
||||
request.key = this.key;
|
||||
request.userVerificationToken = this.userVerificationToken;
|
||||
|
||||
const response = await this.apiService.putTwoFactorAuthenticator(request);
|
||||
await this.processResponse(response);
|
||||
this.onUpdated.emit(true);
|
||||
}
|
||||
|
||||
protected override async disableMethod() {
|
||||
const twoFactorAuthenticatorTokenFeatureFlag = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.AuthenticatorTwoFactorToken,
|
||||
);
|
||||
if (twoFactorAuthenticatorTokenFeatureFlag === false) {
|
||||
return super.disableMethod();
|
||||
}
|
||||
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "disable" },
|
||||
content: { key: "twoStepDisableDesc" },
|
||||
type: "warning",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const request = await this.buildRequestModel(DisableTwoFactorAuthenticatorRequest);
|
||||
request.type = this.type;
|
||||
request.key = this.key;
|
||||
request.userVerificationToken = this.userVerificationToken;
|
||||
await this.apiService.deleteTwoFactorAuthenticator(request);
|
||||
this.enabled = false;
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("twoStepDisabled"));
|
||||
this.onUpdated.emit(false);
|
||||
}
|
||||
|
||||
private async processResponse(response: TwoFactorAuthenticatorResponse) {
|
||||
this.formGroup.get("token").setValue(null);
|
||||
this.enabled = response.enabled;
|
||||
this.key = response.key;
|
||||
this.userVerificationToken = response.userVerificationToken;
|
||||
|
||||
await this.waitForQRiousToLoadOrError().catch((error) => {
|
||||
this.logService.error(error);
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<ng-template body>
|
||||
<tr *ngFor="let i of subscription.items">
|
||||
<td bitCell>
|
||||
{{ i.name }} {{ i.quantity > 1 ? "×" + i.quantity : "" }} @
|
||||
{{ i.name }} {{ i.quantity > 1 ? "×" + i.quantity : "" }} @
|
||||
{{ i.amount | currency: "$" }}
|
||||
</td>
|
||||
<td bitCell>{{ i.quantity * i.amount | currency: "$" }} /{{ i.interval | i18n }}</td>
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
<tr bitRow *ngFor="let i of subscriptionLineItems">
|
||||
<td bitCell [ngClass]="{ 'tw-pl-20': i.addonSubscriptionItem }">
|
||||
<span *ngIf="!i.addonSubscriptionItem">{{ i.productName | i18n }} -</span>
|
||||
{{ i.name }} {{ i.quantity > 1 ? "×" + i.quantity : "" }} @
|
||||
{{ i.name }} {{ i.quantity > 1 ? "×" + i.quantity : "" }} @
|
||||
{{ i.amount | currency: "$" }}
|
||||
</td>
|
||||
<td bitCell class="tw-text-right">
|
||||
|
||||
@@ -17,11 +17,20 @@ import {
|
||||
} from "@bitwarden/angular/services/injection-tokens";
|
||||
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
|
||||
import { ModalService as ModalServiceAbstraction } from "@bitwarden/angular/services/modal.service";
|
||||
import { RegistrationFinishService as RegistrationFinishServiceAbstraction } from "@bitwarden/auth/angular";
|
||||
import {
|
||||
SetPasswordJitService,
|
||||
RegistrationFinishService as RegistrationFinishServiceAbstraction,
|
||||
} from "@bitwarden/auth/angular";
|
||||
import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/account-api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
|
||||
import { ClientType } from "@bitwarden/common/enums";
|
||||
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
@@ -48,7 +57,7 @@ import {
|
||||
import { VaultTimeout, VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type";
|
||||
|
||||
import { PolicyListService } from "../admin-console/core/policy-list.service";
|
||||
import { WebRegistrationFinishService } from "../auth";
|
||||
import { WebSetPasswordJitService, WebRegistrationFinishService } from "../auth";
|
||||
import { AcceptOrganizationInviteService } from "../auth/organization-invite/accept-organization.service";
|
||||
import { HtmlStorageService } from "../core/html-storage.service";
|
||||
import { I18nService } from "../core/i18n.service";
|
||||
@@ -184,6 +193,20 @@ const safeProviders: SafeProvider[] = [
|
||||
PolicyService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: SetPasswordJitService,
|
||||
useClass: WebSetPasswordJitService,
|
||||
deps: [
|
||||
ApiService,
|
||||
CryptoServiceAbstraction,
|
||||
I18nServiceAbstraction,
|
||||
KdfConfigService,
|
||||
InternalMasterPasswordServiceAbstraction,
|
||||
OrganizationApiServiceAbstraction,
|
||||
OrganizationUserService,
|
||||
InternalUserDecryptionOptionsServiceAbstraction,
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
||||
@@ -15,21 +15,45 @@
|
||||
class="tw-mt-2 tw-flex tw-w-full tw-flex-col tw-gap-2 tw-border-0"
|
||||
>
|
||||
<span class="tw-text-xs !tw-text-alt2 tw-p-2 tw-pb-0">{{ "moreFromBitwarden" | i18n }}</span>
|
||||
<a
|
||||
*ngFor="let more of moreProducts"
|
||||
[href]="more.marketingRoute"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="tw-flex tw-py-2 tw-px-4 tw-font-semibold !tw-text-alt2 !tw-no-underline hover:tw-bg-primary-300/60 [&>:not(.bwi)]:hover:tw-underline"
|
||||
>
|
||||
<i class="bwi bwi-fw {{ more.icon }} tw-mt-1 tw-mx-1"></i>
|
||||
<div>
|
||||
{{ more.otherProductOverrides?.name ?? more.name }}
|
||||
<div *ngIf="more.otherProductOverrides?.supportingText" class="tw-text-xs tw-font-normal">
|
||||
{{ more.otherProductOverrides.supportingText }}
|
||||
<ng-container *ngFor="let more of moreProducts">
|
||||
<!-- <a> for when the marketing route is external -->
|
||||
<a
|
||||
*ngIf="more.marketingRoute.external"
|
||||
[href]="more.marketingRoute.route"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="tw-flex tw-py-2 tw-px-4 tw-font-semibold !tw-text-alt2 !tw-no-underline hover:tw-bg-primary-300/60 [&>:not(.bwi)]:hover:tw-underline"
|
||||
>
|
||||
<i class="bwi bwi-fw {{ more.icon }} tw-mt-1 tw-mx-1"></i>
|
||||
<div>
|
||||
{{ more.otherProductOverrides?.name ?? more.name }}
|
||||
<div
|
||||
*ngIf="more.otherProductOverrides?.supportingText"
|
||||
class="tw-text-xs tw-font-normal"
|
||||
>
|
||||
{{ more.otherProductOverrides.supportingText }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</a>
|
||||
<!-- <a> for when the marketing route is internal, it needs to use [routerLink] instead of [href] like the external <a> uses. -->
|
||||
<a
|
||||
*ngIf="!more.marketingRoute.external"
|
||||
[routerLink]="more.marketingRoute.route"
|
||||
rel="noreferrer"
|
||||
class="tw-flex tw-py-2 tw-px-4 tw-font-semibold !tw-text-alt2 !tw-no-underline hover:tw-bg-primary-300/60 [&>:not(.bwi)]:hover:tw-underline"
|
||||
>
|
||||
<i class="bwi bwi-fw {{ more.icon }} tw-mt-1 tw-mx-1"></i>
|
||||
<div>
|
||||
{{ more.otherProductOverrides?.name ?? more.name }}
|
||||
<div
|
||||
*ngIf="more.otherProductOverrides?.supportingText"
|
||||
class="tw-text-xs tw-font-normal"
|
||||
>
|
||||
{{ more.otherProductOverrides.supportingText }}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</ng-container>
|
||||
</section>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@@ -80,7 +80,10 @@ describe("NavigationProductSwitcherComponent", () => {
|
||||
isActive: false,
|
||||
name: "Other Product",
|
||||
icon: "bwi-lock",
|
||||
marketingRoute: "https://www.example.com/",
|
||||
marketingRoute: {
|
||||
route: "https://www.example.com/",
|
||||
external: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -100,7 +103,10 @@ describe("NavigationProductSwitcherComponent", () => {
|
||||
isActive: false,
|
||||
name: "Other Product",
|
||||
icon: "bwi-lock",
|
||||
marketingRoute: "https://www.example.com/",
|
||||
marketingRoute: {
|
||||
route: "https://www.example.com/",
|
||||
external: true,
|
||||
},
|
||||
otherProductOverrides: { name: "Alternate name" },
|
||||
},
|
||||
],
|
||||
@@ -117,7 +123,10 @@ describe("NavigationProductSwitcherComponent", () => {
|
||||
isActive: false,
|
||||
name: "Other Product",
|
||||
icon: "bwi-lock",
|
||||
marketingRoute: "https://www.example.com/",
|
||||
marketingRoute: {
|
||||
route: "https://www.example.com/",
|
||||
external: true,
|
||||
},
|
||||
otherProductOverrides: { name: "Alternate name", supportingText: "Supporting Text" },
|
||||
},
|
||||
],
|
||||
@@ -134,9 +143,27 @@ describe("NavigationProductSwitcherComponent", () => {
|
||||
mockProducts$.next({
|
||||
bento: [],
|
||||
other: [
|
||||
{ name: "AA Product", icon: "bwi-lock", marketingRoute: "https://www.example.com/" },
|
||||
{ name: "Test Product", icon: "bwi-lock", marketingRoute: "https://www.example.com/" },
|
||||
{ name: "Organizations", icon: "bwi-lock", marketingRoute: "https://www.example.com/" },
|
||||
{
|
||||
name: "AA Product",
|
||||
icon: "bwi-lock",
|
||||
marketingRoute: {
|
||||
route: "https://www.example.com/",
|
||||
external: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Product",
|
||||
icon: "bwi-lock",
|
||||
marketingRoute: {
|
||||
route: "https://www.example.com/",
|
||||
external: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Organizations",
|
||||
icon: "bwi-lock",
|
||||
marketingRoute: { route: "https://www.example.com/", external: true },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -157,7 +184,10 @@ describe("NavigationProductSwitcherComponent", () => {
|
||||
{
|
||||
name: "Organizations",
|
||||
icon: "bwi-lock",
|
||||
marketingRoute: "https://www.example.com/",
|
||||
marketingRoute: {
|
||||
route: "https://www.example.com/",
|
||||
external: true,
|
||||
},
|
||||
isActive: true,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -34,17 +34,30 @@
|
||||
class="tw-mt-4 tw-flex tw-w-full tw-flex-col tw-gap-2 tw-border-0 tw-border-t tw-border-solid tw-border-t-text-muted tw-p-2 tw-pb-0"
|
||||
>
|
||||
<span class="tw-mb-1 tw-text-xs tw-text-muted">{{ "moreFromBitwarden" | i18n }}</span>
|
||||
<a
|
||||
*ngFor="let product of products.other"
|
||||
bitLink
|
||||
[href]="product.marketingRoute"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<span class="tw-flex tw-items-center tw-font-normal">
|
||||
<i class="bwi bwi-fw {{ product.icon }} tw-m-0 !tw-mr-3"></i>{{ product.name }}
|
||||
</span>
|
||||
</a>
|
||||
<span *ngFor="let product of products.other">
|
||||
<!-- <a> for when the marketing route is internal, it needs to use [routerLink] instead of [href] like the external <a> uses. -->
|
||||
<a
|
||||
*ngIf="!product.marketingRoute.external"
|
||||
bitLink
|
||||
[routerLink]="product.marketingRoute.route"
|
||||
>
|
||||
<span class="tw-flex tw-items-center tw-font-normal">
|
||||
<i class="bwi bwi-fw {{ product.icon }} tw-m-0 !tw-mr-3"></i>{{ product.name }}
|
||||
</span>
|
||||
</a>
|
||||
<!-- <a> for when the marketing route is external -->
|
||||
<a
|
||||
*ngIf="product.marketingRoute.external"
|
||||
bitLink
|
||||
[href]="product.marketingRoute.route"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<span class="tw-flex tw-items-center tw-font-normal">
|
||||
<i class="bwi bwi-fw {{ product.icon }} tw-m-0 !tw-mr-3"></i>{{ product.name }}
|
||||
</span>
|
||||
</a>
|
||||
</span>
|
||||
</section>
|
||||
</div>
|
||||
</bit-menu>
|
||||
|
||||
@@ -30,7 +30,13 @@ export type ProductSwitcherItem = {
|
||||
/**
|
||||
* Route for items in the `otherProducts$` section
|
||||
*/
|
||||
marketingRoute?: string | any[];
|
||||
marketingRoute?: {
|
||||
route: string | any[];
|
||||
external: boolean;
|
||||
};
|
||||
/**
|
||||
* Route definition for external/internal routes for items in the `otherProducts$` section
|
||||
*/
|
||||
|
||||
/**
|
||||
* Used to apply css styles to show when a button is selected
|
||||
@@ -136,7 +142,10 @@ export class ProductSwitcherService {
|
||||
name: "Password Manager",
|
||||
icon: "bwi-lock",
|
||||
appRoute: "/vault",
|
||||
marketingRoute: "https://bitwarden.com/products/personal/",
|
||||
marketingRoute: {
|
||||
route: "https://bitwarden.com/products/personal/",
|
||||
external: true,
|
||||
},
|
||||
isActive:
|
||||
!this.router.url.includes("/sm/") &&
|
||||
!this.router.url.includes("/organizations/") &&
|
||||
@@ -146,7 +155,10 @@ export class ProductSwitcherService {
|
||||
name: "Secrets Manager",
|
||||
icon: "bwi-cli",
|
||||
appRoute: ["/sm", smOrg?.id],
|
||||
marketingRoute: "https://bitwarden.com/products/secrets-manager/",
|
||||
marketingRoute: {
|
||||
route: "/sm-landing",
|
||||
external: false,
|
||||
},
|
||||
isActive: this.router.url.includes("/sm/"),
|
||||
otherProductOverrides: {
|
||||
supportingText: this.i18n.transform("secureYourInfrastructure"),
|
||||
@@ -156,7 +168,10 @@ export class ProductSwitcherService {
|
||||
name: "Admin Console",
|
||||
icon: "bwi-business",
|
||||
appRoute: ["/organizations", acOrg?.id],
|
||||
marketingRoute: "https://bitwarden.com/products/business/",
|
||||
marketingRoute: {
|
||||
route: "https://bitwarden.com/products/business/",
|
||||
external: true,
|
||||
},
|
||||
isActive: this.router.url.includes("/organizations/"),
|
||||
},
|
||||
provider: {
|
||||
@@ -168,7 +183,10 @@ export class ProductSwitcherService {
|
||||
orgs: {
|
||||
name: "Organizations",
|
||||
icon: "bwi-business",
|
||||
marketingRoute: "https://bitwarden.com/products/business/",
|
||||
marketingRoute: {
|
||||
route: "https://bitwarden.com/products/business/",
|
||||
external: true,
|
||||
},
|
||||
otherProductOverrides: {
|
||||
name: "Share your passwords",
|
||||
supportingText: this.i18n.transform("protectYourFamilyOrBusiness"),
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
RegistrationStartComponent,
|
||||
RegistrationStartSecondaryComponent,
|
||||
RegistrationStartSecondaryComponentData,
|
||||
SetPasswordJitComponent,
|
||||
LockIcon,
|
||||
RegistrationLinkExpiredComponent,
|
||||
} from "@bitwarden/auth/angular";
|
||||
@@ -59,6 +60,8 @@ import { EnvironmentSelectorComponent } from "./components/environment-selector/
|
||||
import { DataProperties } from "./core";
|
||||
import { FrontendLayoutComponent } from "./layouts/frontend-layout.component";
|
||||
import { UserLayoutComponent } from "./layouts/user-layout.component";
|
||||
import { RequestSMAccessComponent } from "./secrets-manager/secrets-manager-landing/request-sm-access.component";
|
||||
import { SMLandingComponent } from "./secrets-manager/secrets-manager-landing/sm-landing.component";
|
||||
import { DomainRulesComponent } from "./settings/domain-rules.component";
|
||||
import { PreferencesComponent } from "./settings/preferences.component";
|
||||
import { GeneratorComponent } from "./tools/generator.component";
|
||||
@@ -206,6 +209,15 @@ const routes: Routes = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "set-password-jit",
|
||||
canActivate: [canAccessFeature(FeatureFlag.EmailVerification)],
|
||||
component: SetPasswordJitComponent,
|
||||
data: {
|
||||
pageTitle: "joinOrganization",
|
||||
pageSubtitle: "finishJoiningThisOrganizationBySettingAMasterPassword",
|
||||
} satisfies AnonLayoutWrapperData,
|
||||
},
|
||||
{
|
||||
path: "signup-link-expired",
|
||||
canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()],
|
||||
@@ -405,6 +417,16 @@ const routes: Routes = [
|
||||
component: SendComponent,
|
||||
data: { titleId: "send" } satisfies DataProperties,
|
||||
},
|
||||
{
|
||||
path: "sm-landing",
|
||||
component: SMLandingComponent,
|
||||
data: { titleId: "moreProductsFromBitwarden" },
|
||||
},
|
||||
{
|
||||
path: "request-sm-access",
|
||||
component: RequestSMAccessComponent,
|
||||
data: { titleId: "requestAccessToSecretsManager" },
|
||||
},
|
||||
{
|
||||
path: "create-organization",
|
||||
component: CreateOrganizationComponent,
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Guid } from "@bitwarden/common/src/types/guid";
|
||||
|
||||
export class RequestSMAccessRequest {
|
||||
OrganizationId: Guid;
|
||||
EmailContent: string;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<app-header></app-header>
|
||||
|
||||
<bit-container>
|
||||
<form [formGroup]="requestAccessForm" [bitSubmit]="submit">
|
||||
<div class="tw-grid tw-grid-cols-12 tw-gap-4">
|
||||
<div class="tw-col-span-9">
|
||||
<p bitTypography="body1">{{ "youNeedApprovalFromYourAdminToTrySecretsManager" | i18n }}</p>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "addANote" | i18n }}</bit-label>
|
||||
<textarea
|
||||
rows="20"
|
||||
id="request_access_textarea"
|
||||
bitInput
|
||||
formControlName="requestAccessEmailContents"
|
||||
></textarea>
|
||||
</bit-form-field>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "organization" | i18n }}</bit-label>
|
||||
<bit-select formControlName="selectedOrganization">
|
||||
<bit-option
|
||||
*ngFor="let org of organizations"
|
||||
[value]="org"
|
||||
[label]="org.name"
|
||||
required
|
||||
></bit-option>
|
||||
</bit-select>
|
||||
</bit-form-field>
|
||||
<div class="tw-flex tw-gap-x-4 tw-mt-4">
|
||||
<button bitButton bitFormButton type="submit" buttonType="primary">
|
||||
{{ "sendRequest" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitButton buttonType="secondary" [routerLink]="'/sm-landing'">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</bit-container>
|
||||
@@ -0,0 +1,75 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { FormControl, FormGroup, Validators } from "@angular/forms";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { Guid } from "@bitwarden/common/types/guid";
|
||||
import { NoItemsModule, SearchModule, ToastService } from "@bitwarden/components";
|
||||
|
||||
import { HeaderModule } from "../../layouts/header/header.module";
|
||||
import { OssModule } from "../../oss.module";
|
||||
import { SharedModule } from "../../shared/shared.module";
|
||||
import { RequestSMAccessRequest } from "../models/requests/request-sm-access.request";
|
||||
|
||||
import { SmLandingApiService } from "./sm-landing-api.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-request-sm-access",
|
||||
standalone: true,
|
||||
templateUrl: "request-sm-access.component.html",
|
||||
imports: [SharedModule, SearchModule, NoItemsModule, HeaderModule, OssModule],
|
||||
})
|
||||
export class RequestSMAccessComponent implements OnInit {
|
||||
requestAccessForm = new FormGroup({
|
||||
requestAccessEmailContents: new FormControl(
|
||||
this.i18nService.t("requestAccessSMDefaultEmailContent"),
|
||||
[Validators.required],
|
||||
),
|
||||
selectedOrganization: new FormControl<Organization>(null, [Validators.required]),
|
||||
});
|
||||
organizations: Organization[] = [];
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private i18nService: I18nService,
|
||||
private organizationService: OrganizationService,
|
||||
private smLandingApiService: SmLandingApiService,
|
||||
private toastService: ToastService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.organizations = (await this.organizationService.getAll())
|
||||
.filter((e) => e.enabled)
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
if (this.organizations === null || this.organizations.length < 1) {
|
||||
await this.navigateToCreateOrganizationPage();
|
||||
}
|
||||
}
|
||||
|
||||
submit = async () => {
|
||||
this.requestAccessForm.markAllAsTouched();
|
||||
if (this.requestAccessForm.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formValue = this.requestAccessForm.value;
|
||||
const request = new RequestSMAccessRequest();
|
||||
request.OrganizationId = formValue.selectedOrganization.id as Guid;
|
||||
request.EmailContent = formValue.requestAccessEmailContents;
|
||||
|
||||
await this.smLandingApiService.requestSMAccessFromAdmins(request);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("smAccessRequestEmailSent"),
|
||||
});
|
||||
await this.router.navigate(["/"]);
|
||||
};
|
||||
|
||||
async navigateToCreateOrganizationPage() {
|
||||
await this.router.navigate(["/create-organization"]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
|
||||
import { RequestSMAccessRequest } from "../models/requests/request-sm-access.request";
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
})
|
||||
export class SmLandingApiService {
|
||||
constructor(private apiService: ApiService) {}
|
||||
|
||||
async requestSMAccessFromAdmins(request: RequestSMAccessRequest): Promise<void> {
|
||||
await this.apiService.send("POST", "/request-access/request-sm-access", request, true, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<app-header></app-header>
|
||||
|
||||
<div class="tw-grid tw-grid-cols-12 tw-gap-4">
|
||||
<div class="tw-col-span-6">
|
||||
<img [src]="imageSrc" class="tw-max-w-full" alt="Bitwarden" aria-hidden="true" />
|
||||
</div>
|
||||
<div class="tw-col-span-6 tw-mx-4">
|
||||
<h1 bitTypography="h1">{{ "bitwardenSecretsManager" | i18n }}</h1>
|
||||
<bit-container *ngIf="this.showSecretsManagerInformation">
|
||||
<p bitTypography="body1">
|
||||
{{ "developmentDevOpsAndITTeamsChooseBWSecret" | i18n }}
|
||||
</p>
|
||||
<ul class="tw-list-outside">
|
||||
<li bitTypography="body1" class="tw-mb-2">
|
||||
<b>{{ "centralizeSecretsManagement" | i18n }}</b>
|
||||
{{ "centralizeSecretsManagementDescription" | i18n }}
|
||||
</li>
|
||||
<li bitTypography="body1" class="tw-mb-2">
|
||||
<b>{{ "preventSecretLeaks" | i18n }}</b> {{ "preventSecretLeaksDescription" | i18n }}
|
||||
</li>
|
||||
<li bitTypography="body1" class="tw-mb-2">
|
||||
<b>{{ "enhanceDeveloperProductivity" | i18n }}</b>
|
||||
{{ "enhanceDeveloperProductivityDescription" | i18n }}
|
||||
</li>
|
||||
<li bitTypography="body1" class="tw-mb-2">
|
||||
<b>{{ "strengthenBusinessSecurity" | i18n }}</b>
|
||||
{{ "strengthenBusinessSecurityDescription" | i18n }}
|
||||
</li>
|
||||
</ul>
|
||||
</bit-container>
|
||||
<bit-container *ngIf="this.showGiveMembersAccessInstructions">
|
||||
<p bitTypography="body1">
|
||||
{{ "giveMembersAccess" | i18n }}
|
||||
</p>
|
||||
<ul class="tw-list-outside">
|
||||
<li bitTypography="body1" class="tw-mb-2">
|
||||
{{ "openYourOrganizations" | i18n }} <b>{{ "members" | i18n }}</b>
|
||||
{{ "viewAndSelectTheMembers" | i18n }}
|
||||
</li>
|
||||
<li bitTypography="body1" class="tw-mb-2">
|
||||
{{ "usingTheMenuSelect" | i18n }} <b>{{ "activateSecretsManager" | i18n }}</b>
|
||||
{{ "toGrantAccessToSelectedMembers" | i18n }}
|
||||
</li>
|
||||
</ul>
|
||||
</bit-container>
|
||||
<button type="button" bitButton buttonType="primary" [routerLink]="tryItNowUrl">
|
||||
{{ "tryItNow" | i18n }}
|
||||
</button>
|
||||
<a bitLink linkType="primary" [href]="learnMoreUrl" target="_blank" class="tw-m-5">
|
||||
{{ "learnMore" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,76 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { NoItemsModule, SearchModule } from "@bitwarden/components";
|
||||
|
||||
import { HeaderModule } from "../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../shared/shared.module";
|
||||
|
||||
@Component({
|
||||
selector: "app-sm-landing",
|
||||
standalone: true,
|
||||
imports: [SharedModule, SearchModule, NoItemsModule, HeaderModule],
|
||||
templateUrl: "sm-landing.component.html",
|
||||
})
|
||||
export class SMLandingComponent {
|
||||
tryItNowUrl: string;
|
||||
learnMoreUrl: string = "https://bitwarden.com/help/secrets-manager-overview/";
|
||||
imageSrc: string = "../images/sm.webp";
|
||||
showSecretsManagerInformation: boolean = true;
|
||||
showGiveMembersAccessInstructions: boolean = false;
|
||||
|
||||
constructor(private organizationService: OrganizationService) {}
|
||||
|
||||
async ngOnInit() {
|
||||
const enabledOrganizations = (await this.organizationService.getAll()).filter((e) => e.enabled);
|
||||
|
||||
if (enabledOrganizations.length > 0) {
|
||||
this.handleEnabledOrganizations(enabledOrganizations);
|
||||
} else {
|
||||
// Person is not part of any orgs they need to be in an organization in order to use SM
|
||||
this.tryItNowUrl = "/create-organization";
|
||||
}
|
||||
}
|
||||
|
||||
private handleEnabledOrganizations(enabledOrganizations: Organization[]) {
|
||||
// People get to this page because SM (Secrets Manager) isn't enabled for them (or the Organization they are a part of)
|
||||
// 1 - SM is enabled for the Organization but not that user
|
||||
//1a - person is Admin+ (Admin or higher) and just needs instructions on how to enable it for themselves
|
||||
//1b - person is beneath admin status and needs to request SM access from Administrators/Owners
|
||||
// 2 - SM is not enabled for the organization yet
|
||||
//2a - person is Owner/Provider - Direct them to the subscription/billing page
|
||||
//2b - person is Admin - Direct them to request access page where an email is sent to owner/admins
|
||||
//2c - person is user - Direct them to request access page where an email is sent to owner/admins
|
||||
|
||||
// We use useSecretsManager because we want to get the first org the person is a part of where SM is enabled but they don't have access enabled yet
|
||||
const adminPlusNeedsInstructionsToEnableSM = enabledOrganizations.find(
|
||||
(o) => o.isAdmin && o.useSecretsManager,
|
||||
);
|
||||
const ownerNeedsToEnableSM = enabledOrganizations.find(
|
||||
(o) => o.isOwner && !o.useSecretsManager,
|
||||
);
|
||||
|
||||
// 1a If Organization has SM Enabled, but this logged in person does not have it enabled, but they are admin+ then give them instructions to enable.
|
||||
if (adminPlusNeedsInstructionsToEnableSM != undefined) {
|
||||
this.showHowToEnableSMForMembers(adminPlusNeedsInstructionsToEnableSM.id);
|
||||
}
|
||||
// 2a Owners can enable SM in the subscription area of Admin Console.
|
||||
else if (ownerNeedsToEnableSM != undefined) {
|
||||
this.tryItNowUrl = `/organizations/${ownerNeedsToEnableSM.id}/billing/subscription`;
|
||||
}
|
||||
// 1b and 2b 2c, they must be lower than an Owner, and they need access, or want their org to have access to SM.
|
||||
else {
|
||||
this.tryItNowUrl = "/request-sm-access";
|
||||
}
|
||||
}
|
||||
|
||||
private showHowToEnableSMForMembers(orgId: string) {
|
||||
this.showGiveMembersAccessInstructions = true;
|
||||
this.showSecretsManagerInformation = false;
|
||||
this.learnMoreUrl =
|
||||
"https://bitwarden.com/help/secrets-manager-quick-start/#give-members-access";
|
||||
this.imageSrc = "../images/sm-give-access.png";
|
||||
this.tryItNowUrl = `/organizations/${orgId}/members`;
|
||||
}
|
||||
}
|
||||
@@ -210,7 +210,7 @@
|
||||
[disabled]="enforcedPasswordPolicyOptions?.useSpecial"
|
||||
attr.aria-label="{{ 'specialCharacters' | i18n }}"
|
||||
/>
|
||||
<label for="special" class="form-check-label">!@#$%^&*</label>
|
||||
<label for="special" class="form-check-label">!@#$%^&*</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
|
||||
BIN
apps/web/src/images/sm-give-access.png
Normal file
BIN
apps/web/src/images/sm-give-access.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 524 KiB |
BIN
apps/web/src/images/sm.webp
Normal file
BIN
apps/web/src/images/sm.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
@@ -3471,6 +3471,9 @@
|
||||
"joinOrganizationDesc": {
|
||||
"message": "You've been invited to join the organization listed above. To accept the invitation, you need to log in or create a new Bitwarden account."
|
||||
},
|
||||
"finishJoiningThisOrganizationBySettingAMasterPassword": {
|
||||
"message": "Finish joining this organization by setting a master password."
|
||||
},
|
||||
"inviteAccepted": {
|
||||
"message": "Invitation accepted"
|
||||
},
|
||||
@@ -4810,6 +4813,75 @@
|
||||
"message": "or",
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more, see how it works, **or** try it now.'"
|
||||
},
|
||||
"developmentDevOpsAndITTeamsChooseBWSecret": {
|
||||
"message": "Development, DevOps, and IT teams choose Bitwarden Secrets Manager to securely manage and deploy their infrastructure and machine secrets."
|
||||
},
|
||||
"centralizeSecretsManagement": {
|
||||
"message": "Centralize secrets management."
|
||||
},
|
||||
"centralizeSecretsManagementDescription": {
|
||||
"message": "Securely store and manage secrets in one location to prevent secret sprawl across your organization."
|
||||
},
|
||||
"preventSecretLeaks": {
|
||||
"message": "Prevent secret leaks."
|
||||
},
|
||||
"preventSecretLeaksDescription": {
|
||||
"message": "Protect secrets with end-to-end encryption. No more hard coding secrets or sharing through .env files."
|
||||
},
|
||||
"enhanceDeveloperProductivity": {
|
||||
"message": "Enhance developer productivity."
|
||||
},
|
||||
"enhanceDeveloperProductivityDescription": {
|
||||
"message": "Programmatically retrieve and deploy secrets at runtime so developers can focus on what matters most, like improving code quality."
|
||||
},
|
||||
"strengthenBusinessSecurity": {
|
||||
"message": "Strengthen business security."
|
||||
},
|
||||
"strengthenBusinessSecurityDescription": {
|
||||
"message": "Maintain tight control over machine and human access to secrets with SSO integrations, event logs, and access rotation."
|
||||
},
|
||||
"tryItNow": {
|
||||
"message": "Try it now"
|
||||
},
|
||||
"sendRequest": {
|
||||
"message": "Send request"
|
||||
},
|
||||
"addANote": {
|
||||
"message": "Add a note"
|
||||
},
|
||||
"bitwardenSecretsManager": {
|
||||
"message": "Bitwarden Secrets Manager"
|
||||
},
|
||||
"moreProductsFromBitwarden": {
|
||||
"message": "More products from Bitwarden"
|
||||
},
|
||||
"requestAccessToSecretsManager": {
|
||||
"message": "Request access to Secrets Manager"
|
||||
},
|
||||
"youNeedApprovalFromYourAdminToTrySecretsManager": {
|
||||
"message": "You need approval from your administrator to try Secrets Manager."
|
||||
},
|
||||
"smAccessRequestEmailSent" : {
|
||||
"message": "Access request for secrets manager email sent to admins."
|
||||
},
|
||||
"requestAccessSMDefaultEmailContent": {
|
||||
"message": "Hi,\n\nI am requesting a subscription to Bitwarden Secrets Manager for our team. Your support would mean a great deal!\n\nBitwarden Secrets Manager is an end-to-end encrypted secrets management solution for securely storing, sharing, and deploying machine credentials like API keys, database passwords, and authentication certificates.\n\nSecrets Manager will help us to:\n\n- Improve security\n- Streamline operations\n- Prevent costly secret leaks\n\nTo request a free trial for our team, please reach out to Bitwarden.\n\nThank you for your help!"
|
||||
},
|
||||
"giveMembersAccess": {
|
||||
"message": "Give members access:"
|
||||
},
|
||||
"viewAndSelectTheMembers" : {
|
||||
"message" :"view and select the members you want to give access to Secrets Manager."
|
||||
},
|
||||
"openYourOrganizations": {
|
||||
"message": "Open your organization's"
|
||||
},
|
||||
"usingTheMenuSelect": {
|
||||
"message": "Using the menu, select"
|
||||
},
|
||||
"toGrantAccessToSelectedMembers": {
|
||||
"message": "to grant access to selected members."
|
||||
},
|
||||
"sendVaultCardTryItNow": {
|
||||
"message": "try it now",
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more, see how it works, or **try it now**.'"
|
||||
|
||||
Reference in New Issue
Block a user