mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 08:13:42 +00:00
Merge branch 'main' into autofill/pm-8027-inline-menu-appears-within-input-fields-that-do-not-relate-to-user-login
This commit is contained in:
41
.github/workflows/brew-bump-cli.yml
vendored
41
.github/workflows/brew-bump-cli.yml
vendored
@@ -1,41 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bump CLI Formula
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- cli-v**
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update-desktop-cask:
|
|
||||||
name: Update Bitwarden CLI Formula
|
|
||||||
runs-on: macos-13
|
|
||||||
steps:
|
|
||||||
- name: Login to Azure
|
|
||||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
|
||||||
with:
|
|
||||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
|
||||||
|
|
||||||
- name: Retrieve secrets
|
|
||||||
id: retrieve-secrets
|
|
||||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
|
||||||
with:
|
|
||||||
keyvault: "bitwarden-ci"
|
|
||||||
secrets: "brew-bump-workflow-pat"
|
|
||||||
|
|
||||||
- name: Update Homebrew formula
|
|
||||||
uses: dawidd6/action-homebrew-bump-formula@baf2b60c51fc1f8453c884b0c61052668a71bd1d # v3.11.0
|
|
||||||
with:
|
|
||||||
# Required, custom GitHub access token with the 'public_repo' and 'workflow' scopes
|
|
||||||
token: ${{ steps.retrieve-secrets.outputs.brew-bump-workflow-pat }}
|
|
||||||
org: bitwarden
|
|
||||||
tap: Homebrew/homebrew-core
|
|
||||||
formula: bitwarden-cli
|
|
||||||
tag: ${{ github.ref }}
|
|
||||||
revision: ${{ github.sha }}
|
|
||||||
force: true
|
|
||||||
@@ -3323,16 +3323,6 @@
|
|||||||
"clearFiltersOrTryAnother": {
|
"clearFiltersOrTryAnother": {
|
||||||
"message": "Clear filters or try another search term"
|
"message": "Clear filters or try another search term"
|
||||||
},
|
},
|
||||||
"copyInfoLabel": {
|
|
||||||
"message": "Copy info, $ITEMNAME$",
|
|
||||||
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
|
|
||||||
"placeholders": {
|
|
||||||
"itemname": {
|
|
||||||
"content": "$1",
|
|
||||||
"example": "Secret Item"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"copyInfoTitle": {
|
"copyInfoTitle": {
|
||||||
"message": "Copy info - $ITEMNAME$",
|
"message": "Copy info - $ITEMNAME$",
|
||||||
"description": "Title for a button that opens a menu with options to copy information from an item.",
|
"description": "Title for a button that opens a menu with options to copy information from an item.",
|
||||||
@@ -3343,16 +3333,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"copyNoteLabel": {
|
|
||||||
"message": "Copy Note, $ITEMNAME$",
|
|
||||||
"description": "Aria label for a button copies a note to the clipboard.",
|
|
||||||
"placeholders": {
|
|
||||||
"itemname": {
|
|
||||||
"content": "$1",
|
|
||||||
"example": "Secret Note Item"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"copyNoteTitle": {
|
"copyNoteTitle": {
|
||||||
"message": "Copy Note - $ITEMNAME$",
|
"message": "Copy Note - $ITEMNAME$",
|
||||||
"description": "Title for a button copies a note to the clipboard.",
|
"description": "Title for a button copies a note to the clipboard.",
|
||||||
@@ -3393,6 +3373,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"autofillTitle": {
|
||||||
|
"message": "Auto-fill - $ITEMNAME$",
|
||||||
|
"description": "Title for a button that auto-fills a login item.",
|
||||||
|
"placeholders": {
|
||||||
|
"itemname": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "Secret Item"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"noValuesToCopy": {
|
||||||
|
"message": "No values to copy"
|
||||||
|
},
|
||||||
"assignCollections": {
|
"assignCollections": {
|
||||||
"message": "Assign collections"
|
"message": "Assign collections"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
map,
|
map,
|
||||||
Observable,
|
Observable,
|
||||||
pairwise,
|
pairwise,
|
||||||
|
startWith,
|
||||||
Subject,
|
Subject,
|
||||||
switchMap,
|
switchMap,
|
||||||
takeUntil,
|
takeUntil,
|
||||||
@@ -150,8 +151,25 @@ export class AccountSecurityComponent implements OnInit {
|
|||||||
timeout = VaultTimeoutStringType.OnRestart;
|
timeout = VaultTimeoutStringType.OnRestart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const initialValues = {
|
||||||
|
vaultTimeout: timeout,
|
||||||
|
vaultTimeoutAction: await firstValueFrom(
|
||||||
|
this.vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$(activeAccount.id),
|
||||||
|
),
|
||||||
|
pin: await this.pinService.isPinSet(activeAccount.id),
|
||||||
|
biometric: await this.vaultTimeoutSettingsService.isBiometricLockSet(),
|
||||||
|
enableAutoBiometricsPrompt: await firstValueFrom(
|
||||||
|
this.biometricStateService.promptAutomatically$,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
this.form.patchValue(initialValues, { emitEvent: false });
|
||||||
|
|
||||||
|
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
|
||||||
|
this.showChangeMasterPass = await this.userVerificationService.hasMasterPassword();
|
||||||
|
|
||||||
this.form.controls.vaultTimeout.valueChanges
|
this.form.controls.vaultTimeout.valueChanges
|
||||||
.pipe(
|
.pipe(
|
||||||
|
startWith(initialValues.vaultTimeout), // emit to init pairwise
|
||||||
pairwise(),
|
pairwise(),
|
||||||
concatMap(async ([previousValue, newValue]) => {
|
concatMap(async ([previousValue, newValue]) => {
|
||||||
await this.saveVaultTimeout(previousValue, newValue);
|
await this.saveVaultTimeout(previousValue, newValue);
|
||||||
@@ -162,6 +180,7 @@ export class AccountSecurityComponent implements OnInit {
|
|||||||
|
|
||||||
this.form.controls.vaultTimeoutAction.valueChanges
|
this.form.controls.vaultTimeoutAction.valueChanges
|
||||||
.pipe(
|
.pipe(
|
||||||
|
startWith(initialValues.vaultTimeoutAction), // emit to init pairwise
|
||||||
pairwise(),
|
pairwise(),
|
||||||
concatMap(async ([previousValue, newValue]) => {
|
concatMap(async ([previousValue, newValue]) => {
|
||||||
await this.saveVaultTimeoutAction(previousValue, newValue);
|
await this.saveVaultTimeoutAction(previousValue, newValue);
|
||||||
@@ -170,24 +189,6 @@ export class AccountSecurityComponent implements OnInit {
|
|||||||
)
|
)
|
||||||
.subscribe();
|
.subscribe();
|
||||||
|
|
||||||
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
|
||||||
|
|
||||||
const initialValues = {
|
|
||||||
vaultTimeout: timeout,
|
|
||||||
vaultTimeoutAction: await firstValueFrom(
|
|
||||||
this.vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$(activeAccount.id),
|
|
||||||
),
|
|
||||||
pin: await this.pinService.isPinSet(userId),
|
|
||||||
biometric: await this.vaultTimeoutSettingsService.isBiometricLockSet(),
|
|
||||||
enableAutoBiometricsPrompt: await firstValueFrom(
|
|
||||||
this.biometricStateService.promptAutomatically$,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
this.form.patchValue(initialValues); // Emit event to initialize `pairwise` operator
|
|
||||||
|
|
||||||
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
|
|
||||||
this.showChangeMasterPass = await this.userVerificationService.hasMasterPassword();
|
|
||||||
|
|
||||||
this.form.controls.pin.valueChanges
|
this.form.controls.pin.valueChanges
|
||||||
.pipe(
|
.pipe(
|
||||||
concatMap(async (value) => {
|
concatMap(async (value) => {
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
type="button"
|
type="button"
|
||||||
bitIconButton="bwi-clone"
|
bitIconButton="bwi-clone"
|
||||||
size="small"
|
size="small"
|
||||||
[attr.aria-label]="'copyInfoLabel' | i18n: cipher.name"
|
[appA11yTitle]="
|
||||||
[title]="'copyInfoTitle' | i18n: cipher.name"
|
hasLoginValues ? ('copyInfoTitle' | i18n: cipher.name) : ('noValuesToCopy' | i18n)
|
||||||
|
"
|
||||||
|
[disabled]="!hasLoginValues"
|
||||||
[bitMenuTriggerFor]="loginOptions"
|
[bitMenuTriggerFor]="loginOptions"
|
||||||
></button>
|
></button>
|
||||||
<bit-menu #loginOptions>
|
<bit-menu #loginOptions>
|
||||||
@@ -25,8 +27,10 @@
|
|||||||
type="button"
|
type="button"
|
||||||
bitIconButton="bwi-clone"
|
bitIconButton="bwi-clone"
|
||||||
size="small"
|
size="small"
|
||||||
[attr.aria-label]="'copyInfoLabel' | i18n: cipher.name"
|
[appA11yTitle]="
|
||||||
[title]="'copyInfoTitle' | i18n: cipher.name"
|
hasCardValues ? ('copyInfoTitle' | i18n: cipher.name) : ('noValuesToCopy' | i18n)
|
||||||
|
"
|
||||||
|
[disabled]="!hasCardValues"
|
||||||
[bitMenuTriggerFor]="cardOptions"
|
[bitMenuTriggerFor]="cardOptions"
|
||||||
></button>
|
></button>
|
||||||
<bit-menu #cardOptions>
|
<bit-menu #cardOptions>
|
||||||
@@ -44,8 +48,10 @@
|
|||||||
type="button"
|
type="button"
|
||||||
bitIconButton="bwi-clone"
|
bitIconButton="bwi-clone"
|
||||||
size="small"
|
size="small"
|
||||||
[attr.aria-label]="'copyInfoLabel' | i18n: cipher.name"
|
[appA11yTitle]="
|
||||||
[title]="'copyInfoTitle' | i18n: cipher.name"
|
hasIdentityValues ? ('copyInfoTitle' | i18n: cipher.name) : ('noValuesToCopy' | i18n)
|
||||||
|
"
|
||||||
|
[disabled]="!hasIdentityValues"
|
||||||
[bitMenuTriggerFor]="identityOptions"
|
[bitMenuTriggerFor]="identityOptions"
|
||||||
></button>
|
></button>
|
||||||
<bit-menu #identityOptions>
|
<bit-menu #identityOptions>
|
||||||
@@ -69,8 +75,9 @@
|
|||||||
type="button"
|
type="button"
|
||||||
bitIconButton="bwi-clone"
|
bitIconButton="bwi-clone"
|
||||||
size="small"
|
size="small"
|
||||||
[attr.aria-label]="'copyNoteLabel' | i18n: cipher.name"
|
[appA11yTitle]="
|
||||||
[title]="'copyNoteTitle' | i18n: cipher.name"
|
hasSecureNoteValue ? ('copyNoteTitle' | i18n: cipher.name) : ('noValuesToCopy' | i18n)
|
||||||
|
"
|
||||||
appCopyField="secureNote"
|
appCopyField="secureNote"
|
||||||
[cipher]="cipher"
|
[cipher]="cipher"
|
||||||
></button>
|
></button>
|
||||||
|
|||||||
@@ -25,5 +25,28 @@ export class ItemCopyActionsComponent {
|
|||||||
|
|
||||||
protected CipherType = CipherType;
|
protected CipherType = CipherType;
|
||||||
|
|
||||||
|
get hasLoginValues() {
|
||||||
|
return (
|
||||||
|
!!this.cipher.login.hasTotp || !!this.cipher.login.password || !!this.cipher.login.username
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasCardValues() {
|
||||||
|
return !!this.cipher.card.code || !!this.cipher.card.number;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasIdentityValues() {
|
||||||
|
return (
|
||||||
|
!!this.cipher.identity.fullAddressForCopy ||
|
||||||
|
!!this.cipher.identity.email ||
|
||||||
|
!!this.cipher.identity.username ||
|
||||||
|
!!this.cipher.identity.phone
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasSecureNoteValue() {
|
||||||
|
return !!this.cipher.notes;
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
[bitMenuTriggerFor]="moreOptions"
|
[bitMenuTriggerFor]="moreOptions"
|
||||||
></button>
|
></button>
|
||||||
<bit-menu #moreOptions>
|
<bit-menu #moreOptions>
|
||||||
<ng-container *ngIf="isLogin && !hideLoginOptions">
|
<ng-container *ngIf="canAutofill && !hideAutofillOptions">
|
||||||
<ng-container *ngIf="autofillAllowed$ | async">
|
<ng-container *ngIf="autofillAllowed$ | async">
|
||||||
<button type="button" bitMenuItem>
|
<button type="button" bitMenuItem>
|
||||||
{{ "autofill" | i18n }}
|
{{ "autofill" | i18n }}
|
||||||
|
|||||||
@@ -33,11 +33,11 @@ export class ItemMoreOptionsComponent {
|
|||||||
cipher: CipherView;
|
cipher: CipherView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag to hide the login specific menu options. Used for login items that are
|
* Flag to hide the autofill menu options. Used for items that are
|
||||||
* already in the autofill list suggestion.
|
* already in the autofill list suggestion.
|
||||||
*/
|
*/
|
||||||
@Input({ transform: booleanAttribute })
|
@Input({ transform: booleanAttribute })
|
||||||
hideLoginOptions: boolean;
|
hideAutofillOptions: boolean;
|
||||||
|
|
||||||
protected autofillAllowed$ = this.vaultPopupItemsService.autofillAllowed$;
|
protected autofillAllowed$ = this.vaultPopupItemsService.autofillAllowed$;
|
||||||
|
|
||||||
@@ -55,8 +55,11 @@ export class ItemMoreOptionsComponent {
|
|||||||
return this.cipher.edit;
|
return this.cipher.edit;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isLogin() {
|
/**
|
||||||
return this.cipher.type === CipherType.Login;
|
* Determines if the cipher can be autofilled.
|
||||||
|
*/
|
||||||
|
get canAutofill() {
|
||||||
|
return [CipherType.Login, CipherType.Card, CipherType.Identity].includes(this.cipher.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
get favoriteText() {
|
get favoriteText() {
|
||||||
@@ -67,7 +70,7 @@ export class ItemMoreOptionsComponent {
|
|||||||
* Determines if the login cipher can be launched in a new browser tab.
|
* Determines if the login cipher can be launched in a new browser tab.
|
||||||
*/
|
*/
|
||||||
get canLaunch() {
|
get canLaunch() {
|
||||||
return this.isLogin && this.cipher.login.canLaunch;
|
return this.cipher.type === CipherType.Login && this.cipher.login.canLaunch;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
[appA11yTitle]="'viewItemTitle' | i18n: cipher.name"
|
[appA11yTitle]="'viewItemTitle' | i18n: cipher.name"
|
||||||
>
|
>
|
||||||
<app-vault-icon slot="start" [cipher]="cipher"></app-vault-icon>
|
<app-vault-icon slot="start" [cipher]="cipher"></app-vault-icon>
|
||||||
{{ cipher.name }}
|
<span data-testid="item-name">{{ cipher.name }}</span>
|
||||||
<i
|
<i
|
||||||
class="bwi bwi-sm"
|
class="bwi bwi-sm"
|
||||||
*ngIf="cipher.organizationId"
|
*ngIf="cipher.organizationId"
|
||||||
@@ -36,12 +36,20 @@
|
|||||||
</a>
|
</a>
|
||||||
<ng-container slot="end">
|
<ng-container slot="end">
|
||||||
<bit-item-action *ngIf="showAutofillButton">
|
<bit-item-action *ngIf="showAutofillButton">
|
||||||
<button type="button" bitBadge variant="primary">{{ "autoFill" | i18n }}</button>
|
<button
|
||||||
|
type="button"
|
||||||
|
bitBadge
|
||||||
|
variant="primary"
|
||||||
|
[title]="'autofillTitle' | i18n: cipher.name"
|
||||||
|
[attr.aria-label]="'autofillTitle' | i18n: cipher.name"
|
||||||
|
>
|
||||||
|
{{ "autoFill" | i18n }}
|
||||||
|
</button>
|
||||||
</bit-item-action>
|
</bit-item-action>
|
||||||
<app-item-copy-actions [cipher]="cipher"></app-item-copy-actions>
|
<app-item-copy-actions [cipher]="cipher"></app-item-copy-actions>
|
||||||
<app-item-more-options
|
<app-item-more-options
|
||||||
[cipher]="cipher"
|
[cipher]="cipher"
|
||||||
[hideLoginOptions]="showAutofillButton"
|
[hideAutofillOptions]="showAutofillButton"
|
||||||
></app-item-more-options>
|
></app-item-more-options>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</bit-item>
|
</bit-item>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { inject, Injectable, NgZone } from "@angular/core";
|
|||||||
import {
|
import {
|
||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
combineLatest,
|
combineLatest,
|
||||||
|
concatMap,
|
||||||
distinctUntilChanged,
|
distinctUntilChanged,
|
||||||
distinctUntilKeyChanged,
|
distinctUntilKeyChanged,
|
||||||
from,
|
from,
|
||||||
@@ -176,7 +177,12 @@ export class VaultPopupItemsService {
|
|||||||
* Ciphers are sorted by name.
|
* Ciphers are sorted by name.
|
||||||
*/
|
*/
|
||||||
remainingCiphers$: Observable<PopupCipherView[]> = this.favoriteCiphers$.pipe(
|
remainingCiphers$: Observable<PopupCipherView[]> = this.favoriteCiphers$.pipe(
|
||||||
withLatestFrom(this._filteredCipherList$, this.autoFillCiphers$),
|
concatMap(
|
||||||
|
(
|
||||||
|
favoriteCiphers, // concatMap->of is used to make withLatestFrom lazy to avoid race conditions with autoFillCiphers$
|
||||||
|
) =>
|
||||||
|
of(favoriteCiphers).pipe(withLatestFrom(this._filteredCipherList$, this.autoFillCiphers$)),
|
||||||
|
),
|
||||||
map(([favoriteCiphers, ciphers, autoFillCiphers]) =>
|
map(([favoriteCiphers, ciphers, autoFillCiphers]) =>
|
||||||
ciphers.filter(
|
ciphers.filter(
|
||||||
(cipher) => !autoFillCiphers.includes(cipher) && !favoriteCiphers.includes(cipher),
|
(cipher) => !autoFillCiphers.includes(cipher) && !favoriteCiphers.includes(cipher),
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { program } from "commander";
|
import { program } from "commander";
|
||||||
|
|
||||||
|
import { OssServeConfigurator } from "./oss-serve-configurator";
|
||||||
import { registerOssPrograms } from "./register-oss-programs";
|
import { registerOssPrograms } from "./register-oss-programs";
|
||||||
|
import { ServeProgram } from "./serve.program";
|
||||||
import { ServiceContainer } from "./service-container";
|
import { ServiceContainer } from "./service-container";
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
@@ -9,6 +11,10 @@ async function main() {
|
|||||||
|
|
||||||
await registerOssPrograms(serviceContainer);
|
await registerOssPrograms(serviceContainer);
|
||||||
|
|
||||||
|
// ServeProgram is registered separately so it can be overridden by bit-cli
|
||||||
|
const serveConfigurator = new OssServeConfigurator(serviceContainer);
|
||||||
|
new ServeProgram(serviceContainer, serveConfigurator).register();
|
||||||
|
|
||||||
program.parse(process.argv);
|
program.parse(process.argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as koaMulter from "@koa/multer";
|
|
||||||
import * as koaRouter from "@koa/router";
|
import * as koaRouter from "@koa/router";
|
||||||
import { OptionValues } from "commander";
|
import { OptionValues } from "commander";
|
||||||
import * as koa from "koa";
|
import * as koa from "koa";
|
||||||
@@ -7,170 +6,14 @@ import * as koaJson from "koa-json";
|
|||||||
|
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
|
||||||
import { ConfirmCommand } from "../admin-console/commands/confirm.command";
|
import { OssServeConfigurator } from "../oss-serve-configurator";
|
||||||
import { ShareCommand } from "../admin-console/commands/share.command";
|
|
||||||
import { LockCommand } from "../auth/commands/lock.command";
|
|
||||||
import { UnlockCommand } from "../auth/commands/unlock.command";
|
|
||||||
import { Response } from "../models/response";
|
|
||||||
import { FileResponse } from "../models/response/file.response";
|
|
||||||
import { ServiceContainer } from "../service-container";
|
import { ServiceContainer } from "../service-container";
|
||||||
import { GenerateCommand } from "../tools/generate.command";
|
|
||||||
import {
|
|
||||||
SendEditCommand,
|
|
||||||
SendCreateCommand,
|
|
||||||
SendDeleteCommand,
|
|
||||||
SendGetCommand,
|
|
||||||
SendListCommand,
|
|
||||||
SendRemovePasswordCommand,
|
|
||||||
} from "../tools/send";
|
|
||||||
import { CreateCommand } from "../vault/create.command";
|
|
||||||
import { DeleteCommand } from "../vault/delete.command";
|
|
||||||
import { SyncCommand } from "../vault/sync.command";
|
|
||||||
|
|
||||||
import { EditCommand } from "./edit.command";
|
|
||||||
import { GetCommand } from "./get.command";
|
|
||||||
import { ListCommand } from "./list.command";
|
|
||||||
import { RestoreCommand } from "./restore.command";
|
|
||||||
import { StatusCommand } from "./status.command";
|
|
||||||
|
|
||||||
export class ServeCommand {
|
export class ServeCommand {
|
||||||
private listCommand: ListCommand;
|
constructor(
|
||||||
private getCommand: GetCommand;
|
protected serviceContainer: ServiceContainer,
|
||||||
private createCommand: CreateCommand;
|
protected serveConfigurator: OssServeConfigurator,
|
||||||
private editCommand: EditCommand;
|
) {}
|
||||||
private generateCommand: GenerateCommand;
|
|
||||||
private shareCommand: ShareCommand;
|
|
||||||
private statusCommand: StatusCommand;
|
|
||||||
private syncCommand: SyncCommand;
|
|
||||||
private deleteCommand: DeleteCommand;
|
|
||||||
private confirmCommand: ConfirmCommand;
|
|
||||||
private restoreCommand: RestoreCommand;
|
|
||||||
private lockCommand: LockCommand;
|
|
||||||
private unlockCommand: UnlockCommand;
|
|
||||||
|
|
||||||
private sendCreateCommand: SendCreateCommand;
|
|
||||||
private sendDeleteCommand: SendDeleteCommand;
|
|
||||||
private sendEditCommand: SendEditCommand;
|
|
||||||
private sendGetCommand: SendGetCommand;
|
|
||||||
private sendListCommand: SendListCommand;
|
|
||||||
private sendRemovePasswordCommand: SendRemovePasswordCommand;
|
|
||||||
|
|
||||||
constructor(protected serviceContainer: ServiceContainer) {
|
|
||||||
this.getCommand = new GetCommand(
|
|
||||||
this.serviceContainer.cipherService,
|
|
||||||
this.serviceContainer.folderService,
|
|
||||||
this.serviceContainer.collectionService,
|
|
||||||
this.serviceContainer.totpService,
|
|
||||||
this.serviceContainer.auditService,
|
|
||||||
this.serviceContainer.cryptoService,
|
|
||||||
this.serviceContainer.stateService,
|
|
||||||
this.serviceContainer.searchService,
|
|
||||||
this.serviceContainer.apiService,
|
|
||||||
this.serviceContainer.organizationService,
|
|
||||||
this.serviceContainer.eventCollectionService,
|
|
||||||
this.serviceContainer.billingAccountProfileStateService,
|
|
||||||
);
|
|
||||||
this.listCommand = new ListCommand(
|
|
||||||
this.serviceContainer.cipherService,
|
|
||||||
this.serviceContainer.folderService,
|
|
||||||
this.serviceContainer.collectionService,
|
|
||||||
this.serviceContainer.organizationService,
|
|
||||||
this.serviceContainer.searchService,
|
|
||||||
this.serviceContainer.organizationUserService,
|
|
||||||
this.serviceContainer.apiService,
|
|
||||||
this.serviceContainer.eventCollectionService,
|
|
||||||
);
|
|
||||||
this.createCommand = new CreateCommand(
|
|
||||||
this.serviceContainer.cipherService,
|
|
||||||
this.serviceContainer.folderService,
|
|
||||||
this.serviceContainer.cryptoService,
|
|
||||||
this.serviceContainer.apiService,
|
|
||||||
this.serviceContainer.folderApiService,
|
|
||||||
this.serviceContainer.billingAccountProfileStateService,
|
|
||||||
this.serviceContainer.organizationService,
|
|
||||||
);
|
|
||||||
this.editCommand = new EditCommand(
|
|
||||||
this.serviceContainer.cipherService,
|
|
||||||
this.serviceContainer.folderService,
|
|
||||||
this.serviceContainer.cryptoService,
|
|
||||||
this.serviceContainer.apiService,
|
|
||||||
this.serviceContainer.folderApiService,
|
|
||||||
);
|
|
||||||
this.generateCommand = new GenerateCommand(
|
|
||||||
this.serviceContainer.passwordGenerationService,
|
|
||||||
this.serviceContainer.stateService,
|
|
||||||
);
|
|
||||||
this.syncCommand = new SyncCommand(this.serviceContainer.syncService);
|
|
||||||
this.statusCommand = new StatusCommand(
|
|
||||||
this.serviceContainer.environmentService,
|
|
||||||
this.serviceContainer.syncService,
|
|
||||||
this.serviceContainer.accountService,
|
|
||||||
this.serviceContainer.authService,
|
|
||||||
);
|
|
||||||
this.deleteCommand = new DeleteCommand(
|
|
||||||
this.serviceContainer.cipherService,
|
|
||||||
this.serviceContainer.folderService,
|
|
||||||
this.serviceContainer.apiService,
|
|
||||||
this.serviceContainer.folderApiService,
|
|
||||||
this.serviceContainer.billingAccountProfileStateService,
|
|
||||||
);
|
|
||||||
this.confirmCommand = new ConfirmCommand(
|
|
||||||
this.serviceContainer.apiService,
|
|
||||||
this.serviceContainer.cryptoService,
|
|
||||||
this.serviceContainer.organizationUserService,
|
|
||||||
);
|
|
||||||
this.restoreCommand = new RestoreCommand(this.serviceContainer.cipherService);
|
|
||||||
this.shareCommand = new ShareCommand(this.serviceContainer.cipherService);
|
|
||||||
this.lockCommand = new LockCommand(this.serviceContainer.vaultTimeoutService);
|
|
||||||
this.unlockCommand = new UnlockCommand(
|
|
||||||
this.serviceContainer.accountService,
|
|
||||||
this.serviceContainer.masterPasswordService,
|
|
||||||
this.serviceContainer.cryptoService,
|
|
||||||
this.serviceContainer.stateService,
|
|
||||||
this.serviceContainer.cryptoFunctionService,
|
|
||||||
this.serviceContainer.apiService,
|
|
||||||
this.serviceContainer.logService,
|
|
||||||
this.serviceContainer.keyConnectorService,
|
|
||||||
this.serviceContainer.environmentService,
|
|
||||||
this.serviceContainer.syncService,
|
|
||||||
this.serviceContainer.organizationApiService,
|
|
||||||
async () => await this.serviceContainer.logout(),
|
|
||||||
this.serviceContainer.kdfConfigService,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.sendCreateCommand = new SendCreateCommand(
|
|
||||||
this.serviceContainer.sendService,
|
|
||||||
this.serviceContainer.environmentService,
|
|
||||||
this.serviceContainer.sendApiService,
|
|
||||||
this.serviceContainer.billingAccountProfileStateService,
|
|
||||||
);
|
|
||||||
this.sendDeleteCommand = new SendDeleteCommand(
|
|
||||||
this.serviceContainer.sendService,
|
|
||||||
this.serviceContainer.sendApiService,
|
|
||||||
);
|
|
||||||
this.sendGetCommand = new SendGetCommand(
|
|
||||||
this.serviceContainer.sendService,
|
|
||||||
this.serviceContainer.environmentService,
|
|
||||||
this.serviceContainer.searchService,
|
|
||||||
this.serviceContainer.cryptoService,
|
|
||||||
);
|
|
||||||
this.sendEditCommand = new SendEditCommand(
|
|
||||||
this.serviceContainer.sendService,
|
|
||||||
this.sendGetCommand,
|
|
||||||
this.serviceContainer.sendApiService,
|
|
||||||
this.serviceContainer.billingAccountProfileStateService,
|
|
||||||
);
|
|
||||||
this.sendListCommand = new SendListCommand(
|
|
||||||
this.serviceContainer.sendService,
|
|
||||||
this.serviceContainer.environmentService,
|
|
||||||
this.serviceContainer.searchService,
|
|
||||||
);
|
|
||||||
this.sendRemovePasswordCommand = new SendRemovePasswordCommand(
|
|
||||||
this.serviceContainer.sendService,
|
|
||||||
this.serviceContainer.sendApiService,
|
|
||||||
this.serviceContainer.environmentService,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async run(options: OptionValues) {
|
async run(options: OptionValues) {
|
||||||
const protectOrigin = !options.disableOriginProtection;
|
const protectOrigin = !options.disableOriginProtection;
|
||||||
@@ -205,207 +48,7 @@ export class ServeCommand {
|
|||||||
.use(koaBodyParser())
|
.use(koaBodyParser())
|
||||||
.use(koaJson({ pretty: false, param: "pretty" }));
|
.use(koaJson({ pretty: false, param: "pretty" }));
|
||||||
|
|
||||||
router.get("/generate", async (ctx, next) => {
|
this.serveConfigurator.configureRouter(router);
|
||||||
const response = await this.generateCommand.run(ctx.request.query);
|
|
||||||
this.processResponse(ctx.response, response);
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/status", async (ctx, next) => {
|
|
||||||
const response = await this.statusCommand.run();
|
|
||||||
this.processResponse(ctx.response, response);
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/list/object/:object", async (ctx, next) => {
|
|
||||||
if (await this.errorIfLocked(ctx.response)) {
|
|
||||||
await next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let response: Response = null;
|
|
||||||
if (ctx.params.object === "send") {
|
|
||||||
response = await this.sendListCommand.run(ctx.request.query);
|
|
||||||
} else {
|
|
||||||
response = await this.listCommand.run(ctx.params.object, ctx.request.query);
|
|
||||||
}
|
|
||||||
this.processResponse(ctx.response, response);
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/send/list", async (ctx, next) => {
|
|
||||||
if (await this.errorIfLocked(ctx.response)) {
|
|
||||||
await next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const response = await this.sendListCommand.run(ctx.request.query);
|
|
||||||
this.processResponse(ctx.response, response);
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post("/sync", async (ctx, next) => {
|
|
||||||
const response = await this.syncCommand.run(ctx.request.query);
|
|
||||||
this.processResponse(ctx.response, response);
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post("/lock", async (ctx, next) => {
|
|
||||||
const response = await this.lockCommand.run();
|
|
||||||
this.processResponse(ctx.response, response);
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post("/unlock", async (ctx, next) => {
|
|
||||||
// Do not allow guessing password location through serve command
|
|
||||||
delete ctx.request.query.passwordFile;
|
|
||||||
delete ctx.request.query.passwordEnv;
|
|
||||||
|
|
||||||
const response = await this.unlockCommand.run(
|
|
||||||
ctx.request.body.password == null ? null : (ctx.request.body.password as string),
|
|
||||||
ctx.request.query,
|
|
||||||
);
|
|
||||||
this.processResponse(ctx.response, response);
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post("/confirm/:object/:id", async (ctx, next) => {
|
|
||||||
if (await this.errorIfLocked(ctx.response)) {
|
|
||||||
await next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const response = await this.confirmCommand.run(
|
|
||||||
ctx.params.object,
|
|
||||||
ctx.params.id,
|
|
||||||
ctx.request.query,
|
|
||||||
);
|
|
||||||
this.processResponse(ctx.response, response);
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post("/restore/:object/:id", async (ctx, next) => {
|
|
||||||
if (await this.errorIfLocked(ctx.response)) {
|
|
||||||
await next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const response = await this.restoreCommand.run(ctx.params.object, ctx.params.id);
|
|
||||||
this.processResponse(ctx.response, response);
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post("/move/:id/:organizationId", async (ctx, next) => {
|
|
||||||
if (await this.errorIfLocked(ctx.response)) {
|
|
||||||
await next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const response = await this.shareCommand.run(
|
|
||||||
ctx.params.id,
|
|
||||||
ctx.params.organizationId,
|
|
||||||
ctx.request.body, // TODO: Check the format of this body for an array of collection ids
|
|
||||||
);
|
|
||||||
this.processResponse(ctx.response, response);
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post("/attachment", koaMulter().single("file"), async (ctx, next) => {
|
|
||||||
if (await this.errorIfLocked(ctx.response)) {
|
|
||||||
await next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const response = await this.createCommand.run(
|
|
||||||
"attachment",
|
|
||||||
ctx.request.body,
|
|
||||||
ctx.request.query,
|
|
||||||
{
|
|
||||||
fileBuffer: ctx.request.file.buffer,
|
|
||||||
fileName: ctx.request.file.originalname,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
this.processResponse(ctx.response, response);
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post("/send/:id/remove-password", async (ctx, next) => {
|
|
||||||
if (await this.errorIfLocked(ctx.response)) {
|
|
||||||
await next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const response = await this.sendRemovePasswordCommand.run(ctx.params.id);
|
|
||||||
this.processResponse(ctx.response, response);
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post("/object/:object", async (ctx, next) => {
|
|
||||||
if (await this.errorIfLocked(ctx.response)) {
|
|
||||||
await next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let response: Response = null;
|
|
||||||
if (ctx.params.object === "send") {
|
|
||||||
response = await this.sendCreateCommand.run(ctx.request.body, ctx.request.query);
|
|
||||||
} else {
|
|
||||||
response = await this.createCommand.run(
|
|
||||||
ctx.params.object,
|
|
||||||
ctx.request.body,
|
|
||||||
ctx.request.query,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.processResponse(ctx.response, response);
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
|
|
||||||
router.put("/object/:object/:id", async (ctx, next) => {
|
|
||||||
if (await this.errorIfLocked(ctx.response)) {
|
|
||||||
await next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let response: Response = null;
|
|
||||||
if (ctx.params.object === "send") {
|
|
||||||
ctx.request.body.id = ctx.params.id;
|
|
||||||
response = await this.sendEditCommand.run(ctx.request.body, ctx.request.query);
|
|
||||||
} else {
|
|
||||||
response = await this.editCommand.run(
|
|
||||||
ctx.params.object,
|
|
||||||
ctx.params.id,
|
|
||||||
ctx.request.body,
|
|
||||||
ctx.request.query,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.processResponse(ctx.response, response);
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/object/:object/:id", async (ctx, next) => {
|
|
||||||
if (await this.errorIfLocked(ctx.response)) {
|
|
||||||
await next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let response: Response = null;
|
|
||||||
if (ctx.params.object === "send") {
|
|
||||||
response = await this.sendGetCommand.run(ctx.params.id, null);
|
|
||||||
} else {
|
|
||||||
response = await this.getCommand.run(ctx.params.object, ctx.params.id, ctx.request.query);
|
|
||||||
}
|
|
||||||
this.processResponse(ctx.response, response);
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
|
|
||||||
router.delete("/object/:object/:id", async (ctx, next) => {
|
|
||||||
if (await this.errorIfLocked(ctx.response)) {
|
|
||||||
await next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let response: Response = null;
|
|
||||||
if (ctx.params.object === "send") {
|
|
||||||
response = await this.sendDeleteCommand.run(ctx.params.id);
|
|
||||||
} else {
|
|
||||||
response = await this.deleteCommand.run(
|
|
||||||
ctx.params.object,
|
|
||||||
ctx.params.id,
|
|
||||||
ctx.request.query,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.processResponse(ctx.response, response);
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
|
|
||||||
server
|
server
|
||||||
.use(router.routes())
|
.use(router.routes())
|
||||||
@@ -414,31 +57,4 @@ export class ServeCommand {
|
|||||||
this.serviceContainer.logService.info("Listening on " + hostname + ":" + port);
|
this.serviceContainer.logService.info("Listening on " + hostname + ":" + port);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private processResponse(res: koa.Response, commandResponse: Response) {
|
|
||||||
if (!commandResponse.success) {
|
|
||||||
res.status = 400;
|
|
||||||
}
|
|
||||||
if (commandResponse.data instanceof FileResponse) {
|
|
||||||
res.body = commandResponse.data.data;
|
|
||||||
res.attachment(commandResponse.data.fileName);
|
|
||||||
res.set("Content-Type", "application/octet-stream");
|
|
||||||
res.set("Content-Length", commandResponse.data.data.length.toString());
|
|
||||||
} else {
|
|
||||||
res.body = commandResponse;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async errorIfLocked(res: koa.Response) {
|
|
||||||
const authed = await this.serviceContainer.stateService.getIsAuthenticated();
|
|
||||||
if (!authed) {
|
|
||||||
this.processResponse(res, Response.error("You are not logged in."));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (await this.serviceContainer.cryptoService.hasUserKey()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.processResponse(res, Response.error("Vault is locked."));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
399
apps/cli/src/oss-serve-configurator.ts
Normal file
399
apps/cli/src/oss-serve-configurator.ts
Normal file
@@ -0,0 +1,399 @@
|
|||||||
|
import * as koaMulter from "@koa/multer";
|
||||||
|
import * as koaRouter from "@koa/router";
|
||||||
|
import * as koa from "koa";
|
||||||
|
|
||||||
|
import { ConfirmCommand } from "./admin-console/commands/confirm.command";
|
||||||
|
import { ShareCommand } from "./admin-console/commands/share.command";
|
||||||
|
import { LockCommand } from "./auth/commands/lock.command";
|
||||||
|
import { UnlockCommand } from "./auth/commands/unlock.command";
|
||||||
|
import { EditCommand } from "./commands/edit.command";
|
||||||
|
import { GetCommand } from "./commands/get.command";
|
||||||
|
import { ListCommand } from "./commands/list.command";
|
||||||
|
import { RestoreCommand } from "./commands/restore.command";
|
||||||
|
import { StatusCommand } from "./commands/status.command";
|
||||||
|
import { Response } from "./models/response";
|
||||||
|
import { FileResponse } from "./models/response/file.response";
|
||||||
|
import { ServiceContainer } from "./service-container";
|
||||||
|
import { GenerateCommand } from "./tools/generate.command";
|
||||||
|
import {
|
||||||
|
SendEditCommand,
|
||||||
|
SendCreateCommand,
|
||||||
|
SendDeleteCommand,
|
||||||
|
SendGetCommand,
|
||||||
|
SendListCommand,
|
||||||
|
SendRemovePasswordCommand,
|
||||||
|
} from "./tools/send";
|
||||||
|
import { CreateCommand } from "./vault/create.command";
|
||||||
|
import { DeleteCommand } from "./vault/delete.command";
|
||||||
|
import { SyncCommand } from "./vault/sync.command";
|
||||||
|
|
||||||
|
export class OssServeConfigurator {
|
||||||
|
private listCommand: ListCommand;
|
||||||
|
private getCommand: GetCommand;
|
||||||
|
private createCommand: CreateCommand;
|
||||||
|
private editCommand: EditCommand;
|
||||||
|
private generateCommand: GenerateCommand;
|
||||||
|
private shareCommand: ShareCommand;
|
||||||
|
private statusCommand: StatusCommand;
|
||||||
|
private syncCommand: SyncCommand;
|
||||||
|
private deleteCommand: DeleteCommand;
|
||||||
|
private confirmCommand: ConfirmCommand;
|
||||||
|
private restoreCommand: RestoreCommand;
|
||||||
|
private lockCommand: LockCommand;
|
||||||
|
private unlockCommand: UnlockCommand;
|
||||||
|
|
||||||
|
private sendCreateCommand: SendCreateCommand;
|
||||||
|
private sendDeleteCommand: SendDeleteCommand;
|
||||||
|
private sendEditCommand: SendEditCommand;
|
||||||
|
private sendGetCommand: SendGetCommand;
|
||||||
|
private sendListCommand: SendListCommand;
|
||||||
|
private sendRemovePasswordCommand: SendRemovePasswordCommand;
|
||||||
|
|
||||||
|
constructor(protected serviceContainer: ServiceContainer) {
|
||||||
|
this.getCommand = new GetCommand(
|
||||||
|
this.serviceContainer.cipherService,
|
||||||
|
this.serviceContainer.folderService,
|
||||||
|
this.serviceContainer.collectionService,
|
||||||
|
this.serviceContainer.totpService,
|
||||||
|
this.serviceContainer.auditService,
|
||||||
|
this.serviceContainer.cryptoService,
|
||||||
|
this.serviceContainer.stateService,
|
||||||
|
this.serviceContainer.searchService,
|
||||||
|
this.serviceContainer.apiService,
|
||||||
|
this.serviceContainer.organizationService,
|
||||||
|
this.serviceContainer.eventCollectionService,
|
||||||
|
this.serviceContainer.billingAccountProfileStateService,
|
||||||
|
);
|
||||||
|
this.listCommand = new ListCommand(
|
||||||
|
this.serviceContainer.cipherService,
|
||||||
|
this.serviceContainer.folderService,
|
||||||
|
this.serviceContainer.collectionService,
|
||||||
|
this.serviceContainer.organizationService,
|
||||||
|
this.serviceContainer.searchService,
|
||||||
|
this.serviceContainer.organizationUserService,
|
||||||
|
this.serviceContainer.apiService,
|
||||||
|
this.serviceContainer.eventCollectionService,
|
||||||
|
);
|
||||||
|
this.createCommand = new CreateCommand(
|
||||||
|
this.serviceContainer.cipherService,
|
||||||
|
this.serviceContainer.folderService,
|
||||||
|
this.serviceContainer.cryptoService,
|
||||||
|
this.serviceContainer.apiService,
|
||||||
|
this.serviceContainer.folderApiService,
|
||||||
|
this.serviceContainer.billingAccountProfileStateService,
|
||||||
|
this.serviceContainer.organizationService,
|
||||||
|
);
|
||||||
|
this.editCommand = new EditCommand(
|
||||||
|
this.serviceContainer.cipherService,
|
||||||
|
this.serviceContainer.folderService,
|
||||||
|
this.serviceContainer.cryptoService,
|
||||||
|
this.serviceContainer.apiService,
|
||||||
|
this.serviceContainer.folderApiService,
|
||||||
|
);
|
||||||
|
this.generateCommand = new GenerateCommand(
|
||||||
|
this.serviceContainer.passwordGenerationService,
|
||||||
|
this.serviceContainer.stateService,
|
||||||
|
);
|
||||||
|
this.syncCommand = new SyncCommand(this.serviceContainer.syncService);
|
||||||
|
this.statusCommand = new StatusCommand(
|
||||||
|
this.serviceContainer.environmentService,
|
||||||
|
this.serviceContainer.syncService,
|
||||||
|
this.serviceContainer.accountService,
|
||||||
|
this.serviceContainer.authService,
|
||||||
|
);
|
||||||
|
this.deleteCommand = new DeleteCommand(
|
||||||
|
this.serviceContainer.cipherService,
|
||||||
|
this.serviceContainer.folderService,
|
||||||
|
this.serviceContainer.apiService,
|
||||||
|
this.serviceContainer.folderApiService,
|
||||||
|
this.serviceContainer.billingAccountProfileStateService,
|
||||||
|
);
|
||||||
|
this.confirmCommand = new ConfirmCommand(
|
||||||
|
this.serviceContainer.apiService,
|
||||||
|
this.serviceContainer.cryptoService,
|
||||||
|
this.serviceContainer.organizationUserService,
|
||||||
|
);
|
||||||
|
this.restoreCommand = new RestoreCommand(this.serviceContainer.cipherService);
|
||||||
|
this.shareCommand = new ShareCommand(this.serviceContainer.cipherService);
|
||||||
|
this.lockCommand = new LockCommand(this.serviceContainer.vaultTimeoutService);
|
||||||
|
this.unlockCommand = new UnlockCommand(
|
||||||
|
this.serviceContainer.accountService,
|
||||||
|
this.serviceContainer.masterPasswordService,
|
||||||
|
this.serviceContainer.cryptoService,
|
||||||
|
this.serviceContainer.stateService,
|
||||||
|
this.serviceContainer.cryptoFunctionService,
|
||||||
|
this.serviceContainer.apiService,
|
||||||
|
this.serviceContainer.logService,
|
||||||
|
this.serviceContainer.keyConnectorService,
|
||||||
|
this.serviceContainer.environmentService,
|
||||||
|
this.serviceContainer.syncService,
|
||||||
|
this.serviceContainer.organizationApiService,
|
||||||
|
async () => await this.serviceContainer.logout(),
|
||||||
|
this.serviceContainer.kdfConfigService,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.sendCreateCommand = new SendCreateCommand(
|
||||||
|
this.serviceContainer.sendService,
|
||||||
|
this.serviceContainer.environmentService,
|
||||||
|
this.serviceContainer.sendApiService,
|
||||||
|
this.serviceContainer.billingAccountProfileStateService,
|
||||||
|
);
|
||||||
|
this.sendDeleteCommand = new SendDeleteCommand(
|
||||||
|
this.serviceContainer.sendService,
|
||||||
|
this.serviceContainer.sendApiService,
|
||||||
|
);
|
||||||
|
this.sendGetCommand = new SendGetCommand(
|
||||||
|
this.serviceContainer.sendService,
|
||||||
|
this.serviceContainer.environmentService,
|
||||||
|
this.serviceContainer.searchService,
|
||||||
|
this.serviceContainer.cryptoService,
|
||||||
|
);
|
||||||
|
this.sendEditCommand = new SendEditCommand(
|
||||||
|
this.serviceContainer.sendService,
|
||||||
|
this.sendGetCommand,
|
||||||
|
this.serviceContainer.sendApiService,
|
||||||
|
this.serviceContainer.billingAccountProfileStateService,
|
||||||
|
);
|
||||||
|
this.sendListCommand = new SendListCommand(
|
||||||
|
this.serviceContainer.sendService,
|
||||||
|
this.serviceContainer.environmentService,
|
||||||
|
this.serviceContainer.searchService,
|
||||||
|
);
|
||||||
|
this.sendRemovePasswordCommand = new SendRemovePasswordCommand(
|
||||||
|
this.serviceContainer.sendService,
|
||||||
|
this.serviceContainer.sendApiService,
|
||||||
|
this.serviceContainer.environmentService,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
configureRouter(router: koaRouter) {
|
||||||
|
router.get("/generate", async (ctx, next) => {
|
||||||
|
const response = await this.generateCommand.run(ctx.request.query);
|
||||||
|
this.processResponse(ctx.response, response);
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/status", async (ctx, next) => {
|
||||||
|
const response = await this.statusCommand.run();
|
||||||
|
this.processResponse(ctx.response, response);
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/list/object/:object", async (ctx, next) => {
|
||||||
|
if (await this.errorIfLocked(ctx.response)) {
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let response: Response = null;
|
||||||
|
if (ctx.params.object === "send") {
|
||||||
|
response = await this.sendListCommand.run(ctx.request.query);
|
||||||
|
} else {
|
||||||
|
response = await this.listCommand.run(ctx.params.object, ctx.request.query);
|
||||||
|
}
|
||||||
|
this.processResponse(ctx.response, response);
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/send/list", async (ctx, next) => {
|
||||||
|
if (await this.errorIfLocked(ctx.response)) {
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const response = await this.sendListCommand.run(ctx.request.query);
|
||||||
|
this.processResponse(ctx.response, response);
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/sync", async (ctx, next) => {
|
||||||
|
const response = await this.syncCommand.run(ctx.request.query);
|
||||||
|
this.processResponse(ctx.response, response);
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/lock", async (ctx, next) => {
|
||||||
|
const response = await this.lockCommand.run();
|
||||||
|
this.processResponse(ctx.response, response);
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/unlock", async (ctx, next) => {
|
||||||
|
// Do not allow guessing password location through serve command
|
||||||
|
delete ctx.request.query.passwordFile;
|
||||||
|
delete ctx.request.query.passwordEnv;
|
||||||
|
|
||||||
|
const response = await this.unlockCommand.run(
|
||||||
|
ctx.request.body.password == null ? null : (ctx.request.body.password as string),
|
||||||
|
ctx.request.query,
|
||||||
|
);
|
||||||
|
this.processResponse(ctx.response, response);
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/confirm/:object/:id", async (ctx, next) => {
|
||||||
|
if (await this.errorIfLocked(ctx.response)) {
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const response = await this.confirmCommand.run(
|
||||||
|
ctx.params.object,
|
||||||
|
ctx.params.id,
|
||||||
|
ctx.request.query,
|
||||||
|
);
|
||||||
|
this.processResponse(ctx.response, response);
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/restore/:object/:id", async (ctx, next) => {
|
||||||
|
if (await this.errorIfLocked(ctx.response)) {
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const response = await this.restoreCommand.run(ctx.params.object, ctx.params.id);
|
||||||
|
this.processResponse(ctx.response, response);
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/move/:id/:organizationId", async (ctx, next) => {
|
||||||
|
if (await this.errorIfLocked(ctx.response)) {
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const response = await this.shareCommand.run(
|
||||||
|
ctx.params.id,
|
||||||
|
ctx.params.organizationId,
|
||||||
|
ctx.request.body, // TODO: Check the format of this body for an array of collection ids
|
||||||
|
);
|
||||||
|
this.processResponse(ctx.response, response);
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/attachment", koaMulter().single("file"), async (ctx, next) => {
|
||||||
|
if (await this.errorIfLocked(ctx.response)) {
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const response = await this.createCommand.run(
|
||||||
|
"attachment",
|
||||||
|
ctx.request.body,
|
||||||
|
ctx.request.query,
|
||||||
|
{
|
||||||
|
fileBuffer: ctx.request.file.buffer,
|
||||||
|
fileName: ctx.request.file.originalname,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
this.processResponse(ctx.response, response);
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/send/:id/remove-password", async (ctx, next) => {
|
||||||
|
if (await this.errorIfLocked(ctx.response)) {
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const response = await this.sendRemovePasswordCommand.run(ctx.params.id);
|
||||||
|
this.processResponse(ctx.response, response);
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/object/:object", async (ctx, next) => {
|
||||||
|
if (await this.errorIfLocked(ctx.response)) {
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let response: Response = null;
|
||||||
|
if (ctx.params.object === "send") {
|
||||||
|
response = await this.sendCreateCommand.run(ctx.request.body, ctx.request.query);
|
||||||
|
} else {
|
||||||
|
response = await this.createCommand.run(
|
||||||
|
ctx.params.object,
|
||||||
|
ctx.request.body,
|
||||||
|
ctx.request.query,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.processResponse(ctx.response, response);
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.put("/object/:object/:id", async (ctx, next) => {
|
||||||
|
if (await this.errorIfLocked(ctx.response)) {
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let response: Response = null;
|
||||||
|
if (ctx.params.object === "send") {
|
||||||
|
ctx.request.body.id = ctx.params.id;
|
||||||
|
response = await this.sendEditCommand.run(ctx.request.body, ctx.request.query);
|
||||||
|
} else {
|
||||||
|
response = await this.editCommand.run(
|
||||||
|
ctx.params.object,
|
||||||
|
ctx.params.id,
|
||||||
|
ctx.request.body,
|
||||||
|
ctx.request.query,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.processResponse(ctx.response, response);
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/object/:object/:id", async (ctx, next) => {
|
||||||
|
if (await this.errorIfLocked(ctx.response)) {
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let response: Response = null;
|
||||||
|
if (ctx.params.object === "send") {
|
||||||
|
response = await this.sendGetCommand.run(ctx.params.id, null);
|
||||||
|
} else {
|
||||||
|
response = await this.getCommand.run(ctx.params.object, ctx.params.id, ctx.request.query);
|
||||||
|
}
|
||||||
|
this.processResponse(ctx.response, response);
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.delete("/object/:object/:id", async (ctx, next) => {
|
||||||
|
if (await this.errorIfLocked(ctx.response)) {
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let response: Response = null;
|
||||||
|
if (ctx.params.object === "send") {
|
||||||
|
response = await this.sendDeleteCommand.run(ctx.params.id);
|
||||||
|
} else {
|
||||||
|
response = await this.deleteCommand.run(
|
||||||
|
ctx.params.object,
|
||||||
|
ctx.params.id,
|
||||||
|
ctx.request.query,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.processResponse(ctx.response, response);
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected processResponse(res: koa.Response, commandResponse: Response) {
|
||||||
|
if (!commandResponse.success) {
|
||||||
|
res.status = 400;
|
||||||
|
}
|
||||||
|
if (commandResponse.data instanceof FileResponse) {
|
||||||
|
res.body = commandResponse.data.data;
|
||||||
|
res.attachment(commandResponse.data.fileName);
|
||||||
|
res.set("Content-Type", "application/octet-stream");
|
||||||
|
res.set("Content-Length", commandResponse.data.data.length.toString());
|
||||||
|
} else {
|
||||||
|
res.body = commandResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async errorIfLocked(res: koa.Response) {
|
||||||
|
const authed = await this.serviceContainer.stateService.getIsAuthenticated();
|
||||||
|
if (!authed) {
|
||||||
|
this.processResponse(res, Response.error("You are not logged in."));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (await this.serviceContainer.cryptoService.hasUserKey()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.processResponse(res, Response.error("Vault is locked."));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,6 @@ import { BaseProgram } from "./base-program";
|
|||||||
import { CompletionCommand } from "./commands/completion.command";
|
import { CompletionCommand } from "./commands/completion.command";
|
||||||
import { ConfigCommand } from "./commands/config.command";
|
import { ConfigCommand } from "./commands/config.command";
|
||||||
import { EncodeCommand } from "./commands/encode.command";
|
import { EncodeCommand } from "./commands/encode.command";
|
||||||
import { ServeCommand } from "./commands/serve.command";
|
|
||||||
import { StatusCommand } from "./commands/status.command";
|
import { StatusCommand } from "./commands/status.command";
|
||||||
import { UpdateCommand } from "./commands/update.command";
|
import { UpdateCommand } from "./commands/update.command";
|
||||||
import { Response } from "./models/response";
|
import { Response } from "./models/response";
|
||||||
@@ -487,34 +486,5 @@ export class Program extends BaseProgram {
|
|||||||
const response = await command.run();
|
const response = await command.run();
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
program
|
|
||||||
.command("serve")
|
|
||||||
.description("Start a RESTful API webserver.")
|
|
||||||
.option("--hostname <hostname>", "The hostname to bind your API webserver to.")
|
|
||||||
.option("--port <port>", "The port to run your API webserver on.")
|
|
||||||
.option(
|
|
||||||
"--disable-origin-protection",
|
|
||||||
"If set, allows requests with origin header. Warning, this option exists for backwards compatibility reasons and exposes your environment to known CSRF attacks.",
|
|
||||||
)
|
|
||||||
.on("--help", () => {
|
|
||||||
writeLn("\n Notes:");
|
|
||||||
writeLn("");
|
|
||||||
writeLn(" Default hostname is `localhost`.");
|
|
||||||
writeLn(" Use hostname `all` for no hostname binding.");
|
|
||||||
writeLn(" Default port is `8087`.");
|
|
||||||
writeLn("");
|
|
||||||
writeLn(" Examples:");
|
|
||||||
writeLn("");
|
|
||||||
writeLn(" bw serve");
|
|
||||||
writeLn(" bw serve --port 8080");
|
|
||||||
writeLn(" bw serve --hostname bwapi.mydomain.com --port 80");
|
|
||||||
writeLn("", true);
|
|
||||||
})
|
|
||||||
.action(async (cmd) => {
|
|
||||||
await this.exitIfNotAuthed();
|
|
||||||
const command = new ServeCommand(this.serviceContainer);
|
|
||||||
await command.run(cmd);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
49
apps/cli/src/serve.program.ts
Normal file
49
apps/cli/src/serve.program.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { program } from "commander";
|
||||||
|
|
||||||
|
import { BaseProgram } from "./base-program";
|
||||||
|
import { ServeCommand } from "./commands/serve.command";
|
||||||
|
import { OssServeConfigurator } from "./oss-serve-configurator";
|
||||||
|
import { ServiceContainer } from "./service-container";
|
||||||
|
import { CliUtils } from "./utils";
|
||||||
|
|
||||||
|
const writeLn = CliUtils.writeLn;
|
||||||
|
|
||||||
|
export class ServeProgram extends BaseProgram {
|
||||||
|
constructor(
|
||||||
|
serviceContainer: ServiceContainer,
|
||||||
|
private configurator: OssServeConfigurator,
|
||||||
|
) {
|
||||||
|
super(serviceContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
register() {
|
||||||
|
program
|
||||||
|
.command("serve")
|
||||||
|
.description("Start a RESTful API webserver.")
|
||||||
|
.option("--hostname <hostname>", "The hostname to bind your API webserver to.")
|
||||||
|
.option("--port <port>", "The port to run your API webserver on.")
|
||||||
|
.option(
|
||||||
|
"--disable-origin-protection",
|
||||||
|
"If set, allows requests with origin header. Warning, this option exists for backwards compatibility reasons and exposes your environment to known CSRF attacks.",
|
||||||
|
)
|
||||||
|
.on("--help", () => {
|
||||||
|
writeLn("\n Notes:");
|
||||||
|
writeLn("");
|
||||||
|
writeLn(" Default hostname is `localhost`.");
|
||||||
|
writeLn(" Use hostname `all` for no hostname binding.");
|
||||||
|
writeLn(" Default port is `8087`.");
|
||||||
|
writeLn("");
|
||||||
|
writeLn(" Examples:");
|
||||||
|
writeLn("");
|
||||||
|
writeLn(" bw serve");
|
||||||
|
writeLn(" bw serve --port 8080");
|
||||||
|
writeLn(" bw serve --hostname bwapi.mydomain.com --port 80");
|
||||||
|
writeLn("", true);
|
||||||
|
})
|
||||||
|
.action(async (cmd) => {
|
||||||
|
await this.exitIfNotAuthed();
|
||||||
|
const command = new ServeCommand(this.serviceContainer, this.configurator);
|
||||||
|
await command.run(cmd);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@bitwarden/desktop",
|
"name": "@bitwarden/desktop",
|
||||||
"description": "A secure and free password manager for all of your devices.",
|
"description": "A secure and free password manager for all of your devices.",
|
||||||
"version": "2024.6.0",
|
"version": "2024.6.1",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"bitwarden",
|
"bitwarden",
|
||||||
"password",
|
"password",
|
||||||
|
|||||||
4
apps/desktop/src/package-lock.json
generated
4
apps/desktop/src/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@bitwarden/desktop",
|
"name": "@bitwarden/desktop",
|
||||||
"version": "2024.6.0",
|
"version": "2024.6.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@bitwarden/desktop",
|
"name": "@bitwarden/desktop",
|
||||||
"version": "2024.6.0",
|
"version": "2024.6.1",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bitwarden/desktop-native": "file:../desktop_native",
|
"@bitwarden/desktop-native": "file:../desktop_native",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "@bitwarden/desktop",
|
"name": "@bitwarden/desktop",
|
||||||
"productName": "Bitwarden",
|
"productName": "Bitwarden",
|
||||||
"description": "A secure and free password manager for all of your devices.",
|
"description": "A secure and free password manager for all of your devices.",
|
||||||
"version": "2024.6.0",
|
"version": "2024.6.1",
|
||||||
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
|
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
|
||||||
"homepage": "https://bitwarden.com",
|
"homepage": "https://bitwarden.com",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
|
|||||||
@@ -0,0 +1,415 @@
|
|||||||
|
import { Directive, ViewChild, ViewContainerRef } from "@angular/core";
|
||||||
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
|
import { FormControl } from "@angular/forms";
|
||||||
|
import { firstValueFrom, lastValueFrom, debounceTime } from "rxjs";
|
||||||
|
|
||||||
|
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
||||||
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
|
import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
|
||||||
|
import {
|
||||||
|
OrganizationUserStatusType,
|
||||||
|
OrganizationUserType,
|
||||||
|
ProviderUserStatusType,
|
||||||
|
ProviderUserType,
|
||||||
|
} from "@bitwarden/common/admin-console/enums";
|
||||||
|
import { ProviderUserUserDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user.response";
|
||||||
|
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||||
|
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.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";
|
||||||
|
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||||
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
import { DialogService, TableDataSource } from "@bitwarden/components";
|
||||||
|
|
||||||
|
import { OrganizationUserView } from "../organizations/core/views/organization-user.view";
|
||||||
|
import { UserConfirmComponent } from "../organizations/manage/user-confirm.component";
|
||||||
|
|
||||||
|
type StatusType = OrganizationUserStatusType | ProviderUserStatusType;
|
||||||
|
|
||||||
|
const MaxCheckedCount = 500;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A refactored copy of BasePeopleComponent, using the component library table and other modern features.
|
||||||
|
* This will replace BasePeopleComponent once all subclasses have been changed over to use this class.
|
||||||
|
*/
|
||||||
|
@Directive()
|
||||||
|
export abstract class NewBasePeopleComponent<
|
||||||
|
UserView extends ProviderUserUserDetailsResponse | OrganizationUserView,
|
||||||
|
> {
|
||||||
|
@ViewChild("confirmTemplate", { read: ViewContainerRef, static: true })
|
||||||
|
confirmModalRef: ViewContainerRef;
|
||||||
|
|
||||||
|
get allCount() {
|
||||||
|
return this.activeUsers != null ? this.activeUsers.length : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get invitedCount() {
|
||||||
|
return this.statusMap.has(this.userStatusType.Invited)
|
||||||
|
? this.statusMap.get(this.userStatusType.Invited).length
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get acceptedCount() {
|
||||||
|
return this.statusMap.has(this.userStatusType.Accepted)
|
||||||
|
? this.statusMap.get(this.userStatusType.Accepted).length
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get confirmedCount() {
|
||||||
|
return this.statusMap.has(this.userStatusType.Confirmed)
|
||||||
|
? this.statusMap.get(this.userStatusType.Confirmed).length
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get revokedCount() {
|
||||||
|
return this.statusMap.has(this.userStatusType.Revoked)
|
||||||
|
? this.statusMap.get(this.userStatusType.Revoked).length
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a banner alerting the admin that users need to be confirmed.
|
||||||
|
*/
|
||||||
|
get showConfirmUsers(): boolean {
|
||||||
|
return (
|
||||||
|
this.activeUsers != null &&
|
||||||
|
this.statusMap != null &&
|
||||||
|
this.activeUsers.length > 1 &&
|
||||||
|
this.confirmedCount > 0 &&
|
||||||
|
this.confirmedCount < 3 &&
|
||||||
|
this.acceptedCount > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get showBulkConfirmUsers(): boolean {
|
||||||
|
return this.acceptedCount > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract userType: typeof OrganizationUserType | typeof ProviderUserType;
|
||||||
|
abstract userStatusType: typeof OrganizationUserStatusType | typeof ProviderUserStatusType;
|
||||||
|
|
||||||
|
protected dataSource = new TableDataSource<UserView>();
|
||||||
|
|
||||||
|
firstLoaded: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hashmap that groups users by their status (invited/accepted/etc). This is used by the toggles to show
|
||||||
|
* user counts and filter data by user status.
|
||||||
|
*/
|
||||||
|
statusMap = new Map<StatusType, UserView[]>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently selected status filter, or null to show all active users.
|
||||||
|
*/
|
||||||
|
status: StatusType | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently executing promise - used to avoid multiple user actions executing at once.
|
||||||
|
*/
|
||||||
|
actionPromise: Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All users, loaded from the server, before any filtering has been applied.
|
||||||
|
*/
|
||||||
|
protected allUsers: UserView[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Active users only, that is, users that are not in the revoked status.
|
||||||
|
*/
|
||||||
|
protected activeUsers: UserView[] = [];
|
||||||
|
|
||||||
|
protected searchControl = new FormControl("", { nonNullable: true });
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected apiService: ApiService,
|
||||||
|
protected i18nService: I18nService,
|
||||||
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
|
protected cryptoService: CryptoService,
|
||||||
|
protected validationService: ValidationService,
|
||||||
|
protected modalService: ModalService,
|
||||||
|
private logService: LogService,
|
||||||
|
protected userNamePipe: UserNamePipe,
|
||||||
|
protected dialogService: DialogService,
|
||||||
|
protected organizationManagementPreferencesService: OrganizationManagementPreferencesService,
|
||||||
|
) {
|
||||||
|
// Connect the search input to the table dataSource filter input
|
||||||
|
this.searchControl.valueChanges
|
||||||
|
.pipe(debounceTime(200), takeUntilDestroyed())
|
||||||
|
.subscribe((v) => (this.dataSource.filter = v));
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract edit(user: UserView): void;
|
||||||
|
abstract getUsers(): Promise<ListResponse<UserView> | UserView[]>;
|
||||||
|
abstract deleteUser(id: string): Promise<void>;
|
||||||
|
abstract revokeUser(id: string): Promise<void>;
|
||||||
|
abstract restoreUser(id: string): Promise<void>;
|
||||||
|
abstract reinviteUser(id: string): Promise<void>;
|
||||||
|
abstract confirmUser(user: UserView, publicKey: Uint8Array): Promise<void>;
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
// Load new users from the server
|
||||||
|
const response = await this.getUsers();
|
||||||
|
|
||||||
|
// Reset and repopulate the statusMap
|
||||||
|
this.statusMap.clear();
|
||||||
|
this.activeUsers = [];
|
||||||
|
for (const status of Utils.iterateEnum(this.userStatusType)) {
|
||||||
|
this.statusMap.set(status, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response instanceof ListResponse) {
|
||||||
|
this.allUsers = response.data != null && response.data.length > 0 ? response.data : [];
|
||||||
|
} else if (Array.isArray(response)) {
|
||||||
|
this.allUsers = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.allUsers.forEach((u) => {
|
||||||
|
if (!this.statusMap.has(u.status)) {
|
||||||
|
this.statusMap.set(u.status, [u]);
|
||||||
|
} else {
|
||||||
|
this.statusMap.get(u.status).push(u);
|
||||||
|
}
|
||||||
|
if (u.status !== this.userStatusType.Revoked) {
|
||||||
|
this.activeUsers.push(u);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter based on UserStatus - this also populates the table on first load
|
||||||
|
this.filter(this.status);
|
||||||
|
|
||||||
|
this.firstLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter the data source by user status.
|
||||||
|
* This overwrites dataSource.data because this filtering needs to apply first, before the search input
|
||||||
|
*/
|
||||||
|
filter(status: StatusType | null) {
|
||||||
|
this.status = status;
|
||||||
|
if (this.status != null) {
|
||||||
|
this.dataSource.data = this.statusMap.get(this.status);
|
||||||
|
} else {
|
||||||
|
this.dataSource.data = this.activeUsers;
|
||||||
|
}
|
||||||
|
// Reset checkbox selection
|
||||||
|
this.selectAll(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkUser(user: UserView, select?: boolean) {
|
||||||
|
(user as any).checked = select == null ? !(user as any).checked : select;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectAll(select: boolean) {
|
||||||
|
if (select) {
|
||||||
|
// Reset checkbox selection first so we know nothing else is selected
|
||||||
|
this.selectAll(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredUsers = this.dataSource.filteredData;
|
||||||
|
|
||||||
|
const selectCount =
|
||||||
|
select && filteredUsers.length > MaxCheckedCount ? MaxCheckedCount : filteredUsers.length;
|
||||||
|
for (let i = 0; i < selectCount; i++) {
|
||||||
|
this.checkUser(filteredUsers[i], select);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invite() {
|
||||||
|
this.edit(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async removeUserConfirmationDialog(user: UserView) {
|
||||||
|
return this.dialogService.openSimpleDialog({
|
||||||
|
title: this.userNamePipe.transform(user),
|
||||||
|
content: { key: "removeUserConfirmation" },
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(user: UserView) {
|
||||||
|
const confirmed = await this.removeUserConfirmationDialog(user);
|
||||||
|
if (!confirmed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.actionPromise = this.deleteUser(user.id);
|
||||||
|
try {
|
||||||
|
await this.actionPromise;
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"success",
|
||||||
|
null,
|
||||||
|
this.i18nService.t("removedUserId", this.userNamePipe.transform(user)),
|
||||||
|
);
|
||||||
|
this.removeUser(user);
|
||||||
|
} catch (e) {
|
||||||
|
this.validationService.showError(e);
|
||||||
|
}
|
||||||
|
this.actionPromise = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async revokeUserConfirmationDialog(user: UserView) {
|
||||||
|
return this.dialogService.openSimpleDialog({
|
||||||
|
title: { key: "revokeAccess", placeholders: [this.userNamePipe.transform(user)] },
|
||||||
|
content: this.revokeWarningMessage(),
|
||||||
|
acceptButtonText: { key: "revokeAccess" },
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async revoke(user: UserView) {
|
||||||
|
const confirmed = await this.revokeUserConfirmationDialog(user);
|
||||||
|
|
||||||
|
if (!confirmed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.actionPromise = this.revokeUser(user.id);
|
||||||
|
try {
|
||||||
|
await this.actionPromise;
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"success",
|
||||||
|
null,
|
||||||
|
this.i18nService.t("revokedUserId", this.userNamePipe.transform(user)),
|
||||||
|
);
|
||||||
|
await this.load();
|
||||||
|
} catch (e) {
|
||||||
|
this.validationService.showError(e);
|
||||||
|
}
|
||||||
|
this.actionPromise = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async restore(user: UserView) {
|
||||||
|
this.actionPromise = this.restoreUser(user.id);
|
||||||
|
try {
|
||||||
|
await this.actionPromise;
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"success",
|
||||||
|
null,
|
||||||
|
this.i18nService.t("restoredUserId", this.userNamePipe.transform(user)),
|
||||||
|
);
|
||||||
|
await this.load();
|
||||||
|
} catch (e) {
|
||||||
|
this.validationService.showError(e);
|
||||||
|
}
|
||||||
|
this.actionPromise = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async reinvite(user: UserView) {
|
||||||
|
if (this.actionPromise != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.actionPromise = this.reinviteUser(user.id);
|
||||||
|
try {
|
||||||
|
await this.actionPromise;
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"success",
|
||||||
|
null,
|
||||||
|
this.i18nService.t("hasBeenReinvited", this.userNamePipe.transform(user)),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
this.validationService.showError(e);
|
||||||
|
}
|
||||||
|
this.actionPromise = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async confirm(user: UserView) {
|
||||||
|
function updateUser(self: NewBasePeopleComponent<UserView>) {
|
||||||
|
user.status = self.userStatusType.Confirmed;
|
||||||
|
const mapIndex = self.statusMap.get(self.userStatusType.Accepted).indexOf(user);
|
||||||
|
if (mapIndex > -1) {
|
||||||
|
self.statusMap.get(self.userStatusType.Accepted).splice(mapIndex, 1);
|
||||||
|
self.statusMap.get(self.userStatusType.Confirmed).push(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmUser = async (publicKey: Uint8Array) => {
|
||||||
|
try {
|
||||||
|
this.actionPromise = this.confirmUser(user, publicKey);
|
||||||
|
await this.actionPromise;
|
||||||
|
updateUser(this);
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"success",
|
||||||
|
null,
|
||||||
|
this.i18nService.t("hasBeenConfirmed", this.userNamePipe.transform(user)),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
this.validationService.showError(e);
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
this.actionPromise = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.actionPromise != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId);
|
||||||
|
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
|
||||||
|
|
||||||
|
const autoConfirm = await firstValueFrom(
|
||||||
|
this.organizationManagementPreferencesService.autoConfirmFingerPrints.state$,
|
||||||
|
);
|
||||||
|
if (autoConfirm == null || !autoConfirm) {
|
||||||
|
const dialogRef = UserConfirmComponent.open(this.dialogService, {
|
||||||
|
data: {
|
||||||
|
name: this.userNamePipe.transform(user),
|
||||||
|
userId: user != null ? user.userId : null,
|
||||||
|
publicKey: publicKey,
|
||||||
|
confirmUser: () => confirmUser(publicKey),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await lastValueFrom(dialogRef.closed);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fingerprint = await this.cryptoService.getFingerprint(user.userId, publicKey);
|
||||||
|
this.logService.info(`User's fingerprint: ${fingerprint.join("-")}`);
|
||||||
|
} catch (e) {
|
||||||
|
this.logService.error(e);
|
||||||
|
}
|
||||||
|
await confirmUser(publicKey);
|
||||||
|
} catch (e) {
|
||||||
|
this.logService.error(`Handled exception: ${e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected revokeWarningMessage(): string {
|
||||||
|
return this.i18nService.t("revokeUserConfirmation");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getCheckedUsers() {
|
||||||
|
return this.dataSource.data.filter((u) => (u as any).checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a user row from the table and all related data sources
|
||||||
|
*/
|
||||||
|
protected removeUser(user: UserView) {
|
||||||
|
let index = this.dataSource.data.indexOf(user);
|
||||||
|
if (index > -1) {
|
||||||
|
// Clone the array so that the setter for dataSource.data is triggered to update the table rendering
|
||||||
|
const updatedData = [...this.dataSource.data];
|
||||||
|
updatedData.splice(index, 1);
|
||||||
|
this.dataSource.data = updatedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = this.allUsers.indexOf(user);
|
||||||
|
if (index > -1) {
|
||||||
|
this.allUsers.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.statusMap.has(user.status)) {
|
||||||
|
index = this.statusMap.get(user.status).indexOf(user);
|
||||||
|
if (index > -1) {
|
||||||
|
this.statusMap.get(user.status).splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { ScrollingModule } from "@angular/cdk/scrolling";
|
||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
|
|
||||||
import { PasswordCalloutComponent } from "@bitwarden/auth/angular";
|
import { PasswordCalloutComponent } from "@bitwarden/auth/angular";
|
||||||
@@ -22,6 +23,7 @@ import { PeopleComponent } from "./people.component";
|
|||||||
MembersRoutingModule,
|
MembersRoutingModule,
|
||||||
UserDialogModule,
|
UserDialogModule,
|
||||||
PasswordCalloutComponent,
|
PasswordCalloutComponent,
|
||||||
|
ScrollingModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
BulkConfirmComponent,
|
BulkConfirmComponent,
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
</bit-toggle>
|
</bit-toggle>
|
||||||
</bit-toggle-group>
|
</bit-toggle-group>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="loading">
|
<ng-container *ngIf="!firstLoaded">
|
||||||
<i
|
<i
|
||||||
class="bwi bwi-spinner bwi-spin text-muted"
|
class="bwi bwi-spinner bwi-spin text-muted"
|
||||||
title="{{ 'loading' | i18n }}"
|
title="{{ 'loading' | i18n }}"
|
||||||
@@ -45,16 +45,9 @@
|
|||||||
></i>
|
></i>
|
||||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container
|
<ng-container *ngIf="firstLoaded">
|
||||||
*ngIf="
|
<p *ngIf="!dataSource.filteredData.length">{{ "noMembersInList" | i18n }}</p>
|
||||||
!loading &&
|
<ng-container *ngIf="dataSource.filteredData.length">
|
||||||
((isPaging$ | async)
|
|
||||||
? pagedUsers
|
|
||||||
: (users | search: searchControl.value : 'name' : 'email' : 'id')) as searchedUsers
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<p *ngIf="!searchedUsers.length">{{ "noMembersInList" | i18n }}</p>
|
|
||||||
<ng-container *ngIf="searchedUsers.length">
|
|
||||||
<app-callout
|
<app-callout
|
||||||
type="info"
|
type="info"
|
||||||
title="{{ 'confirmUsers' | i18n }}"
|
title="{{ 'confirmUsers' | i18n }}"
|
||||||
@@ -63,12 +56,10 @@
|
|||||||
>
|
>
|
||||||
{{ "usersNeedConfirmed" | i18n }}
|
{{ "usersNeedConfirmed" | i18n }}
|
||||||
</app-callout>
|
</app-callout>
|
||||||
<bit-table
|
<!-- The padding on the bottom of the cdk-virtual-scroll-viewport element is required to prevent table row content
|
||||||
infinite-scroll
|
from overflowing the <main> element. -->
|
||||||
[infiniteScrollDistance]="1"
|
<cdk-virtual-scroll-viewport scrollWindow [itemSize]="rowHeight" class="tw-pb-8">
|
||||||
[infiniteScrollDisabled]="!(isPaging$ | async)"
|
<bit-table [dataSource]="dataSource">
|
||||||
(scrolled)="loadMore()"
|
|
||||||
>
|
|
||||||
<ng-container header>
|
<ng-container header>
|
||||||
<tr>
|
<tr>
|
||||||
<th bitCell class="tw-w-20">
|
<th bitCell class="tw-w-20">
|
||||||
@@ -83,9 +74,9 @@
|
|||||||
"all" | i18n
|
"all" | i18n
|
||||||
}}</label>
|
}}</label>
|
||||||
</th>
|
</th>
|
||||||
<th bitCell>{{ "name" | i18n }}</th>
|
<th bitCell bitSortable="email" default>{{ "name" | i18n }}</th>
|
||||||
<th bitCell>{{ (organization.useGroups ? "groups" : "collections") | i18n }}</th>
|
<th bitCell>{{ (organization.useGroups ? "groups" : "collections") | i18n }}</th>
|
||||||
<th bitCell>{{ "role" | i18n }}</th>
|
<th bitCell bitSortable="type">{{ "role" | i18n }}</th>
|
||||||
<th bitCell>{{ "policies" | i18n }}</th>
|
<th bitCell>{{ "policies" | i18n }}</th>
|
||||||
<th bitCell class="tw-w-10">
|
<th bitCell class="tw-w-10">
|
||||||
<button
|
<button
|
||||||
@@ -136,8 +127,13 @@
|
|||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template body>
|
<ng-template body let-rows$>
|
||||||
<tr bitRow *ngFor="let u of searchedUsers" alignContent="middle">
|
<tr
|
||||||
|
bitRow
|
||||||
|
*cdkVirtualFor="let u of rows$"
|
||||||
|
alignContent="middle"
|
||||||
|
[ngClass]="rowHeightClass"
|
||||||
|
>
|
||||||
<td bitCell (click)="checkUser(u)">
|
<td bitCell (click)="checkUser(u)">
|
||||||
<input type="checkbox" bitCheckbox [(ngModel)]="$any(u).checked" />
|
<input type="checkbox" bitCheckbox [(ngModel)]="$any(u).checked" />
|
||||||
</td>
|
</td>
|
||||||
@@ -252,7 +248,9 @@
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<bit-menu-divider
|
<bit-menu-divider
|
||||||
*ngIf="u.status === userStatusType.Accepted || u.status === userStatusType.Invited"
|
*ngIf="
|
||||||
|
u.status === userStatusType.Accepted || u.status === userStatusType.Invited
|
||||||
|
"
|
||||||
></bit-menu-divider>
|
></bit-menu-divider>
|
||||||
<button type="button" bitMenuItem (click)="edit(u, memberTab.Role)">
|
<button type="button" bitMenuItem (click)="edit(u, memberTab.Role)">
|
||||||
<i aria-hidden="true" class="bwi bwi-user"></i> {{ "memberRole" | i18n }}
|
<i aria-hidden="true" class="bwi bwi-user"></i> {{ "memberRole" | i18n }}
|
||||||
@@ -272,7 +270,7 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
bitMenuItem
|
bitMenuItem
|
||||||
(click)="events(u)"
|
(click)="openEventsDialog(u)"
|
||||||
*ngIf="organization.useEvents && u.status === userStatusType.Confirmed"
|
*ngIf="organization.useEvents && u.status === userStatusType.Confirmed"
|
||||||
>
|
>
|
||||||
<i aria-hidden="true" class="bwi bwi-file-text"></i> {{ "eventLogs" | i18n }}
|
<i aria-hidden="true" class="bwi bwi-file-text"></i> {{ "eventLogs" | i18n }}
|
||||||
@@ -313,6 +311,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</bit-table>
|
</bit-table>
|
||||||
|
</cdk-virtual-scroll-viewport>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #addEdit></ng-template>
|
<ng-template #addEdit></ng-template>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Component, ViewChild, ViewContainerRef } from "@angular/core";
|
import { Component, ViewChild, ViewContainerRef } from "@angular/core";
|
||||||
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import {
|
import {
|
||||||
combineLatest,
|
combineLatest,
|
||||||
@@ -9,16 +10,12 @@ import {
|
|||||||
map,
|
map,
|
||||||
Observable,
|
Observable,
|
||||||
shareReplay,
|
shareReplay,
|
||||||
Subject,
|
|
||||||
switchMap,
|
switchMap,
|
||||||
takeUntil,
|
|
||||||
} from "rxjs";
|
} from "rxjs";
|
||||||
|
|
||||||
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
|
|
||||||
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
|
||||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
|
import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
|
||||||
@@ -50,7 +47,7 @@ import { CollectionDetailsResponse } from "@bitwarden/common/vault/models/respon
|
|||||||
import { DialogService, SimpleDialogOptions } from "@bitwarden/components";
|
import { DialogService, SimpleDialogOptions } from "@bitwarden/components";
|
||||||
|
|
||||||
import { openEntityEventsDialog } from "../../../admin-console/organizations/manage/entity-events.component";
|
import { openEntityEventsDialog } from "../../../admin-console/organizations/manage/entity-events.component";
|
||||||
import { BasePeopleComponent } from "../../common/base.people.component";
|
import { NewBasePeopleComponent } from "../../common/new-base.people.component";
|
||||||
import { GroupService } from "../core";
|
import { GroupService } from "../core";
|
||||||
import { OrganizationUserView } from "../core/views/organization-user.view";
|
import { OrganizationUserView } from "../core/views/organization-user.view";
|
||||||
|
|
||||||
@@ -70,7 +67,7 @@ import { ResetPasswordComponent } from "./components/reset-password.component";
|
|||||||
selector: "app-org-people",
|
selector: "app-org-people",
|
||||||
templateUrl: "people.component.html",
|
templateUrl: "people.component.html",
|
||||||
})
|
})
|
||||||
export class PeopleComponent extends BasePeopleComponent<OrganizationUserView> {
|
export class PeopleComponent extends NewBasePeopleComponent<OrganizationUserView> {
|
||||||
@ViewChild("groupsTemplate", { read: ViewContainerRef, static: true })
|
@ViewChild("groupsTemplate", { read: ViewContainerRef, static: true })
|
||||||
groupsModalRef: ViewContainerRef;
|
groupsModalRef: ViewContainerRef;
|
||||||
@ViewChild("confirmTemplate", { read: ViewContainerRef, static: true })
|
@ViewChild("confirmTemplate", { read: ViewContainerRef, static: true })
|
||||||
@@ -95,7 +92,9 @@ export class PeopleComponent extends BasePeopleComponent<OrganizationUserView> {
|
|||||||
|
|
||||||
protected canUseSecretsManager$: Observable<boolean>;
|
protected canUseSecretsManager$: Observable<boolean>;
|
||||||
|
|
||||||
private destroy$ = new Subject<void>();
|
// Fixed sizes used for cdkVirtualScroll
|
||||||
|
protected rowHeight = 62;
|
||||||
|
protected rowHeightClass = `tw-h-[62px]`;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
apiService: ApiService,
|
apiService: ApiService,
|
||||||
@@ -104,12 +103,10 @@ export class PeopleComponent extends BasePeopleComponent<OrganizationUserView> {
|
|||||||
modalService: ModalService,
|
modalService: ModalService,
|
||||||
platformUtilsService: PlatformUtilsService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
cryptoService: CryptoService,
|
cryptoService: CryptoService,
|
||||||
searchService: SearchService,
|
|
||||||
validationService: ValidationService,
|
validationService: ValidationService,
|
||||||
private policyService: PolicyService,
|
private policyService: PolicyService,
|
||||||
private policyApiService: PolicyApiService,
|
private policyApiService: PolicyApiService,
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
searchPipe: SearchPipe,
|
|
||||||
userNamePipe: UserNamePipe,
|
userNamePipe: UserNamePipe,
|
||||||
private syncService: SyncService,
|
private syncService: SyncService,
|
||||||
private organizationService: OrganizationService,
|
private organizationService: OrganizationService,
|
||||||
@@ -124,21 +121,17 @@ export class PeopleComponent extends BasePeopleComponent<OrganizationUserView> {
|
|||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
apiService,
|
apiService,
|
||||||
searchService,
|
|
||||||
i18nService,
|
i18nService,
|
||||||
platformUtilsService,
|
platformUtilsService,
|
||||||
cryptoService,
|
cryptoService,
|
||||||
validationService,
|
validationService,
|
||||||
modalService,
|
modalService,
|
||||||
logService,
|
logService,
|
||||||
searchPipe,
|
|
||||||
userNamePipe,
|
userNamePipe,
|
||||||
dialogService,
|
dialogService,
|
||||||
organizationManagementPreferencesService,
|
organizationManagementPreferencesService,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
async ngOnInit() {
|
|
||||||
const organization$ = this.route.params.pipe(
|
const organization$ = this.route.params.pipe(
|
||||||
concatMap((params) => this.organizationService.get$(params.organizationId)),
|
concatMap((params) => this.organizationService.get$(params.organizationId)),
|
||||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||||
@@ -198,29 +191,19 @@ export class PeopleComponent extends BasePeopleComponent<OrganizationUserView> {
|
|||||||
await this.load();
|
await this.load();
|
||||||
|
|
||||||
this.searchControl.setValue(qParams.search);
|
this.searchControl.setValue(qParams.search);
|
||||||
|
|
||||||
if (qParams.viewEvents != null) {
|
if (qParams.viewEvents != null) {
|
||||||
const user = this.users.filter((u) => u.id === qParams.viewEvents);
|
const user = this.dataSource.data.filter((u) => u.id === qParams.viewEvents);
|
||||||
if (user.length > 0 && user[0].status === OrganizationUserStatusType.Confirmed) {
|
if (user.length > 0 && user[0].status === OrganizationUserStatusType.Confirmed) {
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
this.openEventsDialog(user[0]);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
this.events(user[0]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
takeUntil(this.destroy$),
|
takeUntilDestroyed(),
|
||||||
)
|
)
|
||||||
.subscribe();
|
.subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
this.destroy$.next();
|
|
||||||
this.destroy$.complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
async load() {
|
|
||||||
await super.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
async getUsers(): Promise<OrganizationUserView[]> {
|
async getUsers(): Promise<OrganizationUserView[]> {
|
||||||
let groupsPromise: Promise<Map<string, string>>;
|
let groupsPromise: Promise<Map<string, string>>;
|
||||||
let collectionsPromise: Promise<Map<string, string>>;
|
let collectionsPromise: Promise<Map<string, string>>;
|
||||||
@@ -593,8 +576,8 @@ export class PeopleComponent extends BasePeopleComponent<OrganizationUserView> {
|
|||||||
await this.load();
|
await this.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
async events(user: OrganizationUserView) {
|
openEventsDialog(user: OrganizationUserView) {
|
||||||
await openEntityEventsDialog(this.dialogService, {
|
openEntityEventsDialog(this.dialogService, {
|
||||||
data: {
|
data: {
|
||||||
name: this.userNamePipe.transform(user),
|
name: this.userNamePipe.transform(user),
|
||||||
organizationId: this.organization.id,
|
organizationId: this.organization.id,
|
||||||
|
|||||||
@@ -1,27 +1,14 @@
|
|||||||
<form
|
<form
|
||||||
#form
|
[bitSubmit]="submitForm.bind(null, false)"
|
||||||
(ngSubmit)="submit(false)"
|
|
||||||
[appApiAction]="formPromise"
|
[appApiAction]="formPromise"
|
||||||
class="tw-container tw-mx-auto"
|
|
||||||
[formGroup]="formGroup"
|
[formGroup]="formGroup"
|
||||||
|
class="tw-w-96"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
class="tw-mx-auto tw-mt-5 tw-flex tw-max-w-lg tw-flex-col tw-items-center tw-justify-center tw-p-8"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<img class="logo logo-themed" alt="Bitwarden" />
|
|
||||||
<p class="tw-mx-4 tw-mb-4 tw-mt-3 tw-text-center tw-text-xl">
|
|
||||||
{{ "loginOrCreateNewAccount" | i18n }}
|
|
||||||
</p>
|
|
||||||
<div
|
|
||||||
class="tw-mt-3 tw-rounded-md tw-border tw-border-solid tw-border-secondary-300 tw-bg-background tw-p-6"
|
|
||||||
>
|
|
||||||
<ng-container *ngIf="!validatedEmail">
|
<ng-container *ngIf="!validatedEmail">
|
||||||
<div class="tw-mb-3">
|
<div class="tw-mb-3">
|
||||||
<bit-form-field>
|
<bit-form-field>
|
||||||
<bit-label>{{ "emailAddress" | i18n }}</bit-label>
|
<bit-label>{{ "emailAddress" | i18n }}</bit-label>
|
||||||
<input
|
<input
|
||||||
id="login_input_email"
|
|
||||||
bitInput
|
bitInput
|
||||||
type="email"
|
type="email"
|
||||||
formControlName="email"
|
formControlName="email"
|
||||||
@@ -44,7 +31,6 @@
|
|||||||
type="button"
|
type="button"
|
||||||
buttonType="primary"
|
buttonType="primary"
|
||||||
class="tw-w-full"
|
class="tw-w-full"
|
||||||
[disabled]="form.loading"
|
|
||||||
(click)="validateEmail()"
|
(click)="validateEmail()"
|
||||||
>
|
>
|
||||||
<span> {{ "continue" | i18n }} </span>
|
<span> {{ "continue" | i18n }} </span>
|
||||||
@@ -78,13 +64,7 @@
|
|||||||
<div class="tw-mb-6 tw-h-28">
|
<div class="tw-mb-6 tw-h-28">
|
||||||
<bit-form-field class="!tw-mb-1">
|
<bit-form-field class="!tw-mb-1">
|
||||||
<bit-label>{{ "masterPass" | i18n }}</bit-label>
|
<bit-label>{{ "masterPass" | i18n }}</bit-label>
|
||||||
<input
|
<input type="password" bitInput #masterPasswordInput formControlName="masterPassword" />
|
||||||
id="login_input_master-password"
|
|
||||||
type="password"
|
|
||||||
bitInput
|
|
||||||
#masterPasswordInput
|
|
||||||
formControlName="masterPassword"
|
|
||||||
/>
|
|
||||||
<button type="button" bitSuffix bitIconButton bitPasswordInputToggle></button>
|
<button type="button" bitSuffix bitIconButton bitPasswordInputToggle></button>
|
||||||
</bit-form-field>
|
</bit-form-field>
|
||||||
<a
|
<a
|
||||||
@@ -97,21 +77,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div [hidden]="!showCaptcha()">
|
<div [hidden]="!showCaptcha()">
|
||||||
<iframe
|
<iframe id="hcaptcha_iframe" height="80" sandbox="allow-scripts allow-same-origin"></iframe>
|
||||||
id="hcaptcha_iframe"
|
|
||||||
height="80"
|
|
||||||
sandbox="allow-scripts allow-same-origin"
|
|
||||||
></iframe>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tw-mb-3 tw-flex tw-space-x-4">
|
<div class="tw-mb-3 tw-flex tw-space-x-4">
|
||||||
<button
|
<button bitButton buttonType="primary" bitFormButton type="submit" [block]="true">
|
||||||
bitButton
|
|
||||||
buttonType="primary"
|
|
||||||
type="submit"
|
|
||||||
[block]="true"
|
|
||||||
[loading]="form.loading"
|
|
||||||
>
|
|
||||||
<span> {{ "loginWithMasterPassword" | i18n }} </span>
|
<span> {{ "loginWithMasterPassword" | i18n }} </span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -149,7 +119,4 @@
|
|||||||
<a [routerLink]="[]" (click)="toggleValidateEmail(false)">{{ "notYou" | i18n }}</a>
|
<a [routerLink]="[]" (click)="toggleValidateEmail(false)">{{ "notYou" | i18n }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ export class LoginComponent extends BaseLoginComponent implements OnInit {
|
|||||||
enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions;
|
enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions;
|
||||||
policies: Policy[];
|
policies: Policy[];
|
||||||
showPasswordless = false;
|
showPasswordless = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private acceptOrganizationInviteService: AcceptOrganizationInviteService,
|
private acceptOrganizationInviteService: AcceptOrganizationInviteService,
|
||||||
devicesApiService: DevicesApiServiceAbstraction,
|
devicesApiService: DevicesApiServiceAbstraction,
|
||||||
@@ -92,7 +91,13 @@ export class LoginComponent extends BaseLoginComponent implements OnInit {
|
|||||||
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
|
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
|
||||||
this.showPasswordless = flagEnabled("showPasswordless");
|
this.showPasswordless = flagEnabled("showPasswordless");
|
||||||
}
|
}
|
||||||
|
submitForm = async (showToast = true) => {
|
||||||
|
return await this.submitFormHelper(showToast);
|
||||||
|
};
|
||||||
|
|
||||||
|
private async submitFormHelper(showToast: boolean) {
|
||||||
|
await super.submit(showToast);
|
||||||
|
}
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||||
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||||
|
|||||||
@@ -1,44 +1,16 @@
|
|||||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
|
<form [formGroup]="recoverDeleteForm" [bitSubmit]="submit">
|
||||||
<div class="row justify-content-md-center mt-5">
|
<p bitTypography="body1">{{ "deleteRecoverDesc" | i18n }}</p>
|
||||||
<div class="col-5">
|
<bit-form-field>
|
||||||
<p class="lead text-center mb-4">{{ "deleteAccount" | i18n }}</p>
|
<bit-label>{{ "emailAddress" | i18n }}</bit-label>
|
||||||
<div class="card">
|
<input bitInput appAutofocus appInputVerbatim="false" type="email" formControlName="email" />
|
||||||
<div class="card-body">
|
</bit-form-field>
|
||||||
<p>{{ "deleteRecoverDesc" | i18n }}</p>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="email">{{ "emailAddress" | i18n }}</label>
|
|
||||||
<input
|
|
||||||
id="email"
|
|
||||||
class="form-control"
|
|
||||||
type="text"
|
|
||||||
name="Email"
|
|
||||||
[(ngModel)]="email"
|
|
||||||
required
|
|
||||||
appAutofocus
|
|
||||||
inputmode="email"
|
|
||||||
appInputVerbatim="false"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<hr />
|
<hr />
|
||||||
<div class="d-flex">
|
<div class="tw-flex tw-gap-2">
|
||||||
<button
|
<button type="submit" bitButton bitFormButton buttonType="primary" [block]="true">
|
||||||
type="submit"
|
{{ "submit" | i18n }}
|
||||||
class="btn btn-primary btn-block btn-submit"
|
|
||||||
[disabled]="form.loading"
|
|
||||||
>
|
|
||||||
<span>{{ "submit" | i18n }}</span>
|
|
||||||
<i
|
|
||||||
class="bwi bwi-spinner bwi-spin"
|
|
||||||
title="{{ 'loading' | i18n }}"
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
</button>
|
</button>
|
||||||
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
<a bitButton buttonType="secondary" routerLink="/login" [block]="true">
|
||||||
{{ "cancel" | i18n }}
|
{{ "cancel" | i18n }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
|
import { FormControl, FormGroup, Validators } from "@angular/forms";
|
||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { DeleteRecoverRequest } from "@bitwarden/common/models/request/delete-recover.request";
|
import { DeleteRecoverRequest } from "@bitwarden/common/models/request/delete-recover.request";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.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";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -12,33 +12,27 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
|||||||
templateUrl: "recover-delete.component.html",
|
templateUrl: "recover-delete.component.html",
|
||||||
})
|
})
|
||||||
export class RecoverDeleteComponent {
|
export class RecoverDeleteComponent {
|
||||||
email: string;
|
protected recoverDeleteForm = new FormGroup({
|
||||||
formPromise: Promise<any>;
|
email: new FormControl(null, [Validators.required]),
|
||||||
|
});
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private logService: LogService,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async submit() {
|
submit = async () => {
|
||||||
try {
|
|
||||||
const request = new DeleteRecoverRequest();
|
const request = new DeleteRecoverRequest();
|
||||||
request.email = this.email.trim().toLowerCase();
|
request.email = this.recoverDeleteForm.value.email.trim().toLowerCase();
|
||||||
this.formPromise = this.apiService.postAccountRecoverDelete(request);
|
await this.apiService.postAccountRecoverDelete(request);
|
||||||
await this.formPromise;
|
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
"success",
|
"success",
|
||||||
null,
|
null,
|
||||||
this.i18nService.t("deleteRecoverEmailSent"),
|
this.i18nService.t("deleteRecoverEmailSent"),
|
||||||
);
|
);
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
await this.router.navigate(["/"]);
|
||||||
this.router.navigate(["/"]);
|
};
|
||||||
} catch (e) {
|
|
||||||
this.logService.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,52 +1,22 @@
|
|||||||
<form
|
<form [formGroup]="formGroup" [bitSubmit]="submit" class="tw-container">
|
||||||
#form
|
<div *ngIf="loggingIn">
|
||||||
(ngSubmit)="submit()"
|
|
||||||
class="container"
|
|
||||||
[appApiAction]="initiateSsoFormPromise"
|
|
||||||
ngNativeValidate
|
|
||||||
>
|
|
||||||
<div class="row justify-content-md-center mt-5">
|
|
||||||
<div class="col-5">
|
|
||||||
<img class="logo mb-2 logo-themed" alt="Bitwarden" />
|
|
||||||
<div class="card d-block mt-4">
|
|
||||||
<div class="card-body" *ngIf="loggingIn">
|
|
||||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||||
{{ "loading" | i18n }}
|
{{ "loading" | i18n }}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body" *ngIf="!loggingIn">
|
<div *ngIf="!loggingIn">
|
||||||
<p>{{ "ssoLogInWithOrgIdentifier" | i18n }}</p>
|
<p bitTypography="body1">{{ "ssoLogInWithOrgIdentifier" | i18n }}</p>
|
||||||
<div class="form-group">
|
<bit-form-field>
|
||||||
<label for="identifier">{{ "ssoIdentifier" | i18n }}</label>
|
<bit-label>{{ "ssoIdentifier" | i18n }}</bit-label>
|
||||||
<input
|
<input bitInput type="text" formControlName="identifier" appAutofocus />
|
||||||
id="identifier"
|
</bit-form-field>
|
||||||
class="form-control"
|
|
||||||
type="text"
|
|
||||||
name="Identifier"
|
|
||||||
[(ngModel)]="identifier"
|
|
||||||
required
|
|
||||||
appAutofocus
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<hr />
|
<hr />
|
||||||
<div class="d-flex">
|
<div class="tw-flex tw-gap-2">
|
||||||
<button
|
<button type="submit" bitButton bitFormButton buttonType="primary" [block]="true">
|
||||||
type="submit"
|
{{ "logIn" | i18n }}
|
||||||
class="btn btn-primary btn-block btn-submit"
|
|
||||||
[disabled]="form.loading"
|
|
||||||
>
|
|
||||||
<span> <i class="bwi bwi-sign-in" aria-hidden="true"></i> {{ "logIn" | i18n }} </span>
|
|
||||||
<i
|
|
||||||
class="bwi bwi-spinner bwi-spin"
|
|
||||||
title="{{ 'loading' | i18n }}"
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
</button>
|
</button>
|
||||||
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
<a bitButton buttonType="secondary" routerLink="/login" [block]="true">
|
||||||
{{ "cancel" | i18n }}
|
{{ "cancel" | i18n }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
|
import { FormControl, FormGroup, Validators } from "@angular/forms";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { first } from "rxjs/operators";
|
import { first } from "rxjs/operators";
|
||||||
|
|
||||||
@@ -31,6 +32,14 @@ import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/ge
|
|||||||
})
|
})
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||||
export class SsoComponent extends BaseSsoComponent {
|
export class SsoComponent extends BaseSsoComponent {
|
||||||
|
protected formGroup = new FormGroup({
|
||||||
|
identifier: new FormControl(null, [Validators.required]),
|
||||||
|
});
|
||||||
|
|
||||||
|
get identifierFormControl() {
|
||||||
|
return this.formGroup.controls.identifier;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
ssoLoginService: SsoLoginServiceAbstraction,
|
ssoLoginService: SsoLoginServiceAbstraction,
|
||||||
loginStrategyService: LoginStrategyServiceAbstraction,
|
loginStrategyService: LoginStrategyServiceAbstraction,
|
||||||
@@ -82,7 +91,7 @@ export class SsoComponent extends BaseSsoComponent {
|
|||||||
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||||
if (qParams.identifier != null) {
|
if (qParams.identifier != null) {
|
||||||
// SSO Org Identifier in query params takes precedence over claimed domains
|
// SSO Org Identifier in query params takes precedence over claimed domains
|
||||||
this.identifier = qParams.identifier;
|
this.identifierFormControl.setValue(qParams.identifier);
|
||||||
} else {
|
} else {
|
||||||
// Note: this flow is written for web but both browser and desktop
|
// Note: this flow is written for web but both browser and desktop
|
||||||
// redirect here on SSO button click.
|
// redirect here on SSO button click.
|
||||||
@@ -96,7 +105,7 @@ export class SsoComponent extends BaseSsoComponent {
|
|||||||
await this.orgDomainApiService.getClaimedOrgDomainByEmail(qParams.email);
|
await this.orgDomainApiService.getClaimedOrgDomainByEmail(qParams.email);
|
||||||
|
|
||||||
if (response?.ssoAvailable) {
|
if (response?.ssoAvailable) {
|
||||||
this.identifier = response.organizationIdentifier;
|
this.identifierFormControl.setValue(response.organizationIdentifier);
|
||||||
await this.submit();
|
await this.submit();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -110,7 +119,7 @@ export class SsoComponent extends BaseSsoComponent {
|
|||||||
// Fallback to state svc if domain is unclaimed
|
// Fallback to state svc if domain is unclaimed
|
||||||
const storedIdentifier = await this.ssoLoginService.getOrganizationSsoIdentifier();
|
const storedIdentifier = await this.ssoLoginService.getOrganizationSsoIdentifier();
|
||||||
if (storedIdentifier != null) {
|
if (storedIdentifier != null) {
|
||||||
this.identifier = storedIdentifier;
|
this.identifierFormControl.setValue(storedIdentifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -131,13 +140,12 @@ export class SsoComponent extends BaseSsoComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
submit = async () => {
|
||||||
|
this.identifier = this.identifierFormControl.value;
|
||||||
await this.ssoLoginService.setOrganizationSsoIdentifier(this.identifier);
|
await this.ssoLoginService.setOrganizationSsoIdentifier(this.identifier);
|
||||||
if (this.clientId === "browser") {
|
if (this.clientId === "browser") {
|
||||||
document.cookie = `ssoHandOffMessage=${this.i18nService.t("ssoHandOff")};SameSite=strict`;
|
document.cookie = `ssoHandOffMessage=${this.i18nService.t("ssoHandOff")};SameSite=strict`;
|
||||||
}
|
}
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
await Object.getPrototypeOf(this).submit.call(this);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
};
|
||||||
super.submit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export class OrgBillingHistoryViewComponent implements OnInit, OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.billing = await this.organizationApiService.getBilling(this.organizationId);
|
this.billing = await this.organizationApiService.getBillingHistory(this.organizationId);
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ const routes: Routes = [
|
|||||||
children: [], // Children lets us have an empty component.
|
children: [], // Children lets us have an empty component.
|
||||||
canActivate: [redirectGuard()], // Redirects either to vault, login, or lock page.
|
canActivate: [redirectGuard()], // Redirects either to vault, login, or lock page.
|
||||||
},
|
},
|
||||||
{ path: "login", component: LoginComponent, canActivate: [UnauthGuard] },
|
|
||||||
{
|
{
|
||||||
path: "login-with-device",
|
path: "login-with-device",
|
||||||
component: LoginViaAuthRequestComponent,
|
component: LoginViaAuthRequestComponent,
|
||||||
@@ -98,12 +97,6 @@ const routes: Routes = [
|
|||||||
redirectTo: "register",
|
redirectTo: "register",
|
||||||
pathMatch: "full",
|
pathMatch: "full",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "sso",
|
|
||||||
component: SsoComponent,
|
|
||||||
canActivate: [UnauthGuard],
|
|
||||||
data: { titleId: "enterpriseSingleSignOn" } satisfies DataProperties,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "set-password",
|
path: "set-password",
|
||||||
component: SetPasswordComponent,
|
component: SetPasswordComponent,
|
||||||
@@ -134,12 +127,6 @@ const routes: Routes = [
|
|||||||
data: { titleId: "acceptFamilySponsorship", doNotSaveUrl: false } satisfies DataProperties,
|
data: { titleId: "acceptFamilySponsorship", doNotSaveUrl: false } satisfies DataProperties,
|
||||||
},
|
},
|
||||||
{ path: "recover", pathMatch: "full", redirectTo: "recover-2fa" },
|
{ path: "recover", pathMatch: "full", redirectTo: "recover-2fa" },
|
||||||
{
|
|
||||||
path: "recover-delete",
|
|
||||||
component: RecoverDeleteComponent,
|
|
||||||
canActivate: [UnauthGuard],
|
|
||||||
data: { titleId: "deleteAccount" } satisfies DataProperties,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "verify-recover-delete",
|
path: "verify-recover-delete",
|
||||||
component: VerifyRecoverDeleteComponent,
|
component: VerifyRecoverDeleteComponent,
|
||||||
@@ -188,6 +175,43 @@ const routes: Routes = [
|
|||||||
path: "",
|
path: "",
|
||||||
component: AnonLayoutWrapperComponent,
|
component: AnonLayoutWrapperComponent,
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
path: "sso",
|
||||||
|
canActivate: [unauthGuardFn()],
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
component: SsoComponent,
|
||||||
|
data: {
|
||||||
|
pageTitle: "enterpriseSingleSignOn",
|
||||||
|
titleId: "enterpriseSingleSignOn",
|
||||||
|
} satisfies DataProperties & AnonLayoutWrapperData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
component: EnvironmentSelectorComponent,
|
||||||
|
outlet: "environment-selector",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "login",
|
||||||
|
canActivate: [unauthGuardFn()],
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
component: LoginComponent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
component: EnvironmentSelectorComponent,
|
||||||
|
outlet: "environment-selector",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
data: {
|
||||||
|
pageTitle: "logIn",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "2fa",
|
path: "2fa",
|
||||||
component: TwoFactorComponent,
|
component: TwoFactorComponent,
|
||||||
@@ -231,6 +255,20 @@ const routes: Routes = [
|
|||||||
(mod) => mod.AcceptEmergencyComponent,
|
(mod) => mod.AcceptEmergencyComponent,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "recover-delete",
|
||||||
|
canActivate: [unauthGuardFn()],
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
component: RecoverDeleteComponent,
|
||||||
|
data: {
|
||||||
|
pageTitle: "deleteAccount",
|
||||||
|
titleId: "deleteAccount",
|
||||||
|
} satisfies DataProperties & AnonLayoutWrapperData,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
component: EnvironmentSelectorComponent,
|
component: EnvironmentSelectorComponent,
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { MessageResponse } from "@bitwarden/cli/models/response/message.response
|
|||||||
import { OrganizationService } from "@bitwarden/common/admin-console/services/organization/organization.service";
|
import { OrganizationService } from "@bitwarden/common/admin-console/services/organization/organization.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
|
||||||
|
import { ServiceContainer } from "../../service-container";
|
||||||
|
|
||||||
export class ApproveAllCommand {
|
export class ApproveAllCommand {
|
||||||
constructor(
|
constructor(
|
||||||
private organizationAuthRequestService: OrganizationAuthRequestService,
|
private organizationAuthRequestService: OrganizationAuthRequestService,
|
||||||
@@ -49,4 +51,11 @@ export class ApproveAllCommand {
|
|||||||
return Response.error(e);
|
return Response.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static create(serviceContainer: ServiceContainer) {
|
||||||
|
return new ApproveAllCommand(
|
||||||
|
serviceContainer.organizationAuthRequestService,
|
||||||
|
serviceContainer.organizationService,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
|
|||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
|
||||||
import { OrganizationAuthRequestService } from "../../../../bit-common/src/admin-console/auth-requests";
|
import { OrganizationAuthRequestService } from "../../../../bit-common/src/admin-console/auth-requests";
|
||||||
|
import { ServiceContainer } from "../../service-container";
|
||||||
|
|
||||||
export class ApproveCommand {
|
export class ApproveCommand {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -51,4 +52,11 @@ export class ApproveCommand {
|
|||||||
return Response.error(e);
|
return Response.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static create(serviceContainer: ServiceContainer) {
|
||||||
|
return new ApproveCommand(
|
||||||
|
serviceContainer.organizationService,
|
||||||
|
serviceContainer.organizationAuthRequestService,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
|
|||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
|
||||||
import { OrganizationAuthRequestService } from "../../../../bit-common/src/admin-console/auth-requests";
|
import { OrganizationAuthRequestService } from "../../../../bit-common/src/admin-console/auth-requests";
|
||||||
|
import { ServiceContainer } from "../../service-container";
|
||||||
|
|
||||||
export class DenyAllCommand {
|
export class DenyAllCommand {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -46,4 +47,11 @@ export class DenyAllCommand {
|
|||||||
return Response.error(e);
|
return Response.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static create(serviceContainer: ServiceContainer) {
|
||||||
|
return new DenyAllCommand(
|
||||||
|
serviceContainer.organizationService,
|
||||||
|
serviceContainer.organizationAuthRequestService,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
|
|||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
|
||||||
import { OrganizationAuthRequestService } from "../../../../bit-common/src/admin-console/auth-requests";
|
import { OrganizationAuthRequestService } from "../../../../bit-common/src/admin-console/auth-requests";
|
||||||
|
import { ServiceContainer } from "../../service-container";
|
||||||
|
|
||||||
export class DenyCommand {
|
export class DenyCommand {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -43,4 +44,11 @@ export class DenyCommand {
|
|||||||
return Response.error(e);
|
return Response.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static create(serviceContainer: ServiceContainer) {
|
||||||
|
return new DenyCommand(
|
||||||
|
serviceContainer.organizationService,
|
||||||
|
serviceContainer.organizationAuthRequestService,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,11 +42,7 @@ export class DeviceApprovalProgram extends BaseProgram {
|
|||||||
await this.exitIfFeatureFlagDisabled(FeatureFlag.BulkDeviceApproval);
|
await this.exitIfFeatureFlagDisabled(FeatureFlag.BulkDeviceApproval);
|
||||||
await this.exitIfLocked();
|
await this.exitIfLocked();
|
||||||
|
|
||||||
const cmd = new ListCommand(
|
const cmd = ListCommand.create(this.serviceContainer);
|
||||||
this.serviceContainer.organizationAuthRequestService,
|
|
||||||
this.serviceContainer.organizationService,
|
|
||||||
);
|
|
||||||
|
|
||||||
const response = await cmd.run(options.organizationid);
|
const response = await cmd.run(options.organizationid);
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
});
|
});
|
||||||
@@ -61,10 +57,7 @@ export class DeviceApprovalProgram extends BaseProgram {
|
|||||||
await this.exitIfFeatureFlagDisabled(FeatureFlag.BulkDeviceApproval);
|
await this.exitIfFeatureFlagDisabled(FeatureFlag.BulkDeviceApproval);
|
||||||
await this.exitIfLocked();
|
await this.exitIfLocked();
|
||||||
|
|
||||||
const cmd = new ApproveCommand(
|
const cmd = ApproveCommand.create(this.serviceContainer);
|
||||||
this.serviceContainer.organizationService,
|
|
||||||
this.serviceContainer.organizationAuthRequestService,
|
|
||||||
);
|
|
||||||
const response = await cmd.run(options.organizationid, id);
|
const response = await cmd.run(options.organizationid, id);
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
});
|
});
|
||||||
@@ -78,10 +71,7 @@ export class DeviceApprovalProgram extends BaseProgram {
|
|||||||
await this.exitIfFeatureFlagDisabled(FeatureFlag.BulkDeviceApproval);
|
await this.exitIfFeatureFlagDisabled(FeatureFlag.BulkDeviceApproval);
|
||||||
await this.exitIfLocked();
|
await this.exitIfLocked();
|
||||||
|
|
||||||
const cmd = new ApproveAllCommand(
|
const cmd = ApproveAllCommand.create(this.serviceContainer);
|
||||||
this.serviceContainer.organizationAuthRequestService,
|
|
||||||
this.serviceContainer.organizationService,
|
|
||||||
);
|
|
||||||
const response = await cmd.run(options.organizationid);
|
const response = await cmd.run(options.organizationid);
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
});
|
});
|
||||||
@@ -96,10 +86,7 @@ export class DeviceApprovalProgram extends BaseProgram {
|
|||||||
await this.exitIfFeatureFlagDisabled(FeatureFlag.BulkDeviceApproval);
|
await this.exitIfFeatureFlagDisabled(FeatureFlag.BulkDeviceApproval);
|
||||||
await this.exitIfLocked();
|
await this.exitIfLocked();
|
||||||
|
|
||||||
const cmd = new DenyCommand(
|
const cmd = DenyCommand.create(this.serviceContainer);
|
||||||
this.serviceContainer.organizationService,
|
|
||||||
this.serviceContainer.organizationAuthRequestService,
|
|
||||||
);
|
|
||||||
const response = await cmd.run(options.organizationid, id);
|
const response = await cmd.run(options.organizationid, id);
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
});
|
});
|
||||||
@@ -113,10 +100,7 @@ export class DeviceApprovalProgram extends BaseProgram {
|
|||||||
await this.exitIfFeatureFlagDisabled(FeatureFlag.BulkDeviceApproval);
|
await this.exitIfFeatureFlagDisabled(FeatureFlag.BulkDeviceApproval);
|
||||||
await this.exitIfLocked();
|
await this.exitIfLocked();
|
||||||
|
|
||||||
const cmd = new DenyAllCommand(
|
const cmd = DenyAllCommand.create(this.serviceContainer);
|
||||||
this.serviceContainer.organizationService,
|
|
||||||
this.serviceContainer.organizationAuthRequestService,
|
|
||||||
);
|
|
||||||
const response = await cmd.run(options.organizationid);
|
const response = await cmd.run(options.organizationid);
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1 +1,6 @@
|
|||||||
export { DeviceApprovalProgram } from "./device-approval.program";
|
export * from "./device-approval.program";
|
||||||
|
export * from "./approve.command";
|
||||||
|
export * from "./approve-all.command";
|
||||||
|
export * from "./deny.command";
|
||||||
|
export * from "./deny-all.command";
|
||||||
|
export * from "./list.command";
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { ListResponse } from "@bitwarden/cli/models/response/list.response";
|
|||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
|
||||||
|
import { ServiceContainer } from "../../service-container";
|
||||||
|
|
||||||
import { PendingAuthRequestResponse } from "./pending-auth-request.response";
|
import { PendingAuthRequestResponse } from "./pending-auth-request.response";
|
||||||
|
|
||||||
export class ListCommand {
|
export class ListCommand {
|
||||||
@@ -39,4 +41,11 @@ export class ListCommand {
|
|||||||
return Response.error(e);
|
return Response.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static create(serviceContainer: ServiceContainer) {
|
||||||
|
return new ListCommand(
|
||||||
|
serviceContainer.organizationAuthRequestService,
|
||||||
|
serviceContainer.organizationService,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
95
bitwarden_license/bit-cli/src/bit-serve-configurator.ts
Normal file
95
bitwarden_license/bit-cli/src/bit-serve-configurator.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import * as koaRouter from "@koa/router";
|
||||||
|
|
||||||
|
import { OssServeConfigurator } from "@bitwarden/cli/oss-serve-configurator";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ApproveAllCommand,
|
||||||
|
ApproveCommand,
|
||||||
|
DenyAllCommand,
|
||||||
|
DenyCommand,
|
||||||
|
ListCommand,
|
||||||
|
} from "./admin-console/device-approval";
|
||||||
|
import { ServiceContainer } from "./service-container";
|
||||||
|
|
||||||
|
export class BitServeConfigurator extends OssServeConfigurator {
|
||||||
|
constructor(protected override serviceContainer: ServiceContainer) {
|
||||||
|
super(serviceContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
override configureRouter(router: koaRouter): void {
|
||||||
|
// Register OSS endpoints
|
||||||
|
super.configureRouter(router);
|
||||||
|
|
||||||
|
// Register bit endpoints
|
||||||
|
this.serveDeviceApprovals(router);
|
||||||
|
}
|
||||||
|
|
||||||
|
private serveDeviceApprovals(router: koaRouter) {
|
||||||
|
router.get("/device-approval/:organizationId", async (ctx, next) => {
|
||||||
|
if (await this.errorIfLocked(ctx.response)) {
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await ListCommand.create(this.serviceContainer).run(
|
||||||
|
ctx.params.organizationId,
|
||||||
|
);
|
||||||
|
this.processResponse(ctx.response, response);
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/device-approval/:organizationId/approve-all", async (ctx, next) => {
|
||||||
|
if (await this.errorIfLocked(ctx.response)) {
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await ApproveAllCommand.create(this.serviceContainer).run(
|
||||||
|
ctx.params.organizationId,
|
||||||
|
);
|
||||||
|
this.processResponse(ctx.response, response);
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/device-approval/:organizationId/approve/:requestId", async (ctx, next) => {
|
||||||
|
if (await this.errorIfLocked(ctx.response)) {
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await ApproveCommand.create(this.serviceContainer).run(
|
||||||
|
ctx.params.organizationId,
|
||||||
|
ctx.params.requestId,
|
||||||
|
);
|
||||||
|
this.processResponse(ctx.response, response);
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/device-approval/:organizationId/deny-all", async (ctx, next) => {
|
||||||
|
if (await this.errorIfLocked(ctx.response)) {
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await DenyAllCommand.create(this.serviceContainer).run(
|
||||||
|
ctx.params.organizationId,
|
||||||
|
);
|
||||||
|
this.processResponse(ctx.response, response);
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/device-approval/:organizationId/deny/:requestId", async (ctx, next) => {
|
||||||
|
if (await this.errorIfLocked(ctx.response)) {
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await DenyCommand.create(this.serviceContainer).run(
|
||||||
|
ctx.params.organizationId,
|
||||||
|
ctx.params.requestId,
|
||||||
|
);
|
||||||
|
this.processResponse(ctx.response, response);
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { program } from "commander";
|
import { program } from "commander";
|
||||||
|
|
||||||
import { registerOssPrograms } from "@bitwarden/cli/register-oss-programs";
|
import { registerOssPrograms } from "@bitwarden/cli/register-oss-programs";
|
||||||
|
import { ServeProgram } from "@bitwarden/cli/serve.program";
|
||||||
|
|
||||||
|
import { BitServeConfigurator } from "./bit-serve-configurator";
|
||||||
import { registerBitPrograms } from "./register-bit-programs";
|
import { registerBitPrograms } from "./register-bit-programs";
|
||||||
import { ServiceContainer } from "./service-container";
|
import { ServiceContainer } from "./service-container";
|
||||||
|
|
||||||
@@ -12,6 +14,9 @@ async function main() {
|
|||||||
await registerOssPrograms(serviceContainer);
|
await registerOssPrograms(serviceContainer);
|
||||||
await registerBitPrograms(serviceContainer);
|
await registerBitPrograms(serviceContainer);
|
||||||
|
|
||||||
|
const serveConfigurator = new BitServeConfigurator(serviceContainer);
|
||||||
|
new ServeProgram(serviceContainer, serveConfigurator).register();
|
||||||
|
|
||||||
program.parse(process.argv);
|
program.parse(process.argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,46 +1,41 @@
|
|||||||
<div class="mt-5 d-flex justify-content-center" *ngIf="loading">
|
<div class="tw-mt-5 tw-flex tw-justify-center" *ngIf="loading">
|
||||||
<div>
|
<div>
|
||||||
<img class="mb-4 logo logo-themed" alt="Bitwarden" />
|
<img class="tw-mb-4 logo logo-themed" alt="Bitwarden" />
|
||||||
<p class="text-center">
|
<p class="tw-text-center">
|
||||||
<i
|
<i
|
||||||
class="bwi bwi-spinner bwi-spin bwi-2x text-muted"
|
class="bwi bwi-spinner bwi-spin bwi-2x tw-text-muted"
|
||||||
title="{{ 'loading' | i18n }}"
|
title="{{ 'loading' | i18n }}"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
></i>
|
></i>
|
||||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="container" *ngIf="!loading && !authed">
|
<div *ngIf="!loading && !authed">
|
||||||
<div class="row justify-content-md-center mt-5">
|
<p bitTypography="body1" class="tw-text-center">
|
||||||
<div class="col-5">
|
|
||||||
<p class="lead text-center mb-4">{{ "joinProvider" | i18n }}</p>
|
|
||||||
<div class="card d-block">
|
|
||||||
<div class="card-body">
|
|
||||||
<p class="text-center">
|
|
||||||
{{ providerName }}
|
{{ providerName }}
|
||||||
<strong class="d-block mt-2">{{ email }}</strong>
|
<span bitTypography="body1" class="tw-font-bold">{{ email }}</span>
|
||||||
</p>
|
</p>
|
||||||
<p>{{ "joinProviderDesc" | i18n }}</p>
|
<p bitTypography="body1">{{ "joinProviderDesc" | i18n }}</p>
|
||||||
<hr />
|
<hr />
|
||||||
<div class="d-flex">
|
<div class="tw-flex tw-gap-2">
|
||||||
<a
|
<a
|
||||||
|
bitButton
|
||||||
|
buttonType="primary"
|
||||||
routerLink="/login"
|
routerLink="/login"
|
||||||
[queryParams]="{ email: email }"
|
[queryParams]="{ email: email }"
|
||||||
class="btn btn-primary btn-block"
|
[block]="true"
|
||||||
>
|
>
|
||||||
{{ "logIn" | i18n }}
|
{{ "logIn" | i18n }}
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
|
bitButton
|
||||||
|
buttonType="primary"
|
||||||
routerLink="/register"
|
routerLink="/register"
|
||||||
[queryParams]="{ email: email }"
|
[queryParams]="{ email: email }"
|
||||||
class="btn btn-primary btn-block ml-2 mt-0"
|
[block]="true"
|
||||||
>
|
>
|
||||||
{{ "createAccount" | i18n }}
|
{{ "createAccount" | i18n }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { NgModule } from "@angular/core";
|
|||||||
import { RouterModule, Routes } from "@angular/router";
|
import { RouterModule, Routes } from "@angular/router";
|
||||||
|
|
||||||
import { AuthGuard } from "@bitwarden/angular/auth/guards";
|
import { AuthGuard } from "@bitwarden/angular/auth/guards";
|
||||||
|
import { AnonLayoutWrapperComponent } from "@bitwarden/auth/angular";
|
||||||
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
||||||
import { ProvidersComponent } from "@bitwarden/web-vault/app/admin-console/providers/providers.component";
|
import { ProvidersComponent } from "@bitwarden/web-vault/app/admin-console/providers/providers.component";
|
||||||
import { FrontendLayoutComponent } from "@bitwarden/web-vault/app/layouts/frontend-layout.component";
|
import { FrontendLayoutComponent } from "@bitwarden/web-vault/app/layouts/frontend-layout.component";
|
||||||
@@ -48,10 +49,19 @@ const routes: Routes = [
|
|||||||
component: SetupProviderComponent,
|
component: SetupProviderComponent,
|
||||||
data: { titleId: "setupProvider" },
|
data: { titleId: "setupProvider" },
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
component: AnonLayoutWrapperComponent,
|
||||||
|
children: [
|
||||||
{
|
{
|
||||||
path: "accept-provider",
|
path: "accept-provider",
|
||||||
component: AcceptProviderComponent,
|
component: AcceptProviderComponent,
|
||||||
data: { titleId: "acceptProvider" },
|
data: {
|
||||||
|
pageTitle: "joinProvider",
|
||||||
|
titleId: "acceptProvider",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -119,9 +119,15 @@ export class SetupComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
submit = async () => {
|
submit = async () => {
|
||||||
try {
|
try {
|
||||||
|
const consolidatedBillingEnabled = await firstValueFrom(this.enableConsolidatedBilling$);
|
||||||
|
|
||||||
this.formGroup.markAllAsTouched();
|
this.formGroup.markAllAsTouched();
|
||||||
const taxInformationValid = this.manageTaxInformationComponent.touch();
|
|
||||||
if (this.formGroup.invalid || !taxInformationValid) {
|
const formIsValid = consolidatedBillingEnabled
|
||||||
|
? this.formGroup.valid && this.manageTaxInformationComponent.touch()
|
||||||
|
: this.formGroup.valid;
|
||||||
|
|
||||||
|
if (!formIsValid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,9 +140,7 @@ export class SetupComponent implements OnInit, OnDestroy {
|
|||||||
request.token = this.token;
|
request.token = this.token;
|
||||||
request.key = key;
|
request.key = key;
|
||||||
|
|
||||||
const enableConsolidatedBilling = await firstValueFrom(this.enableConsolidatedBilling$);
|
if (consolidatedBillingEnabled) {
|
||||||
|
|
||||||
if (enableConsolidatedBilling) {
|
|
||||||
request.taxInfo = new ExpandedTaxInfoUpdateRequest();
|
request.taxInfo = new ExpandedTaxInfoUpdateRequest();
|
||||||
const taxInformation = this.manageTaxInformationComponent.getTaxInformation();
|
const taxInformation = this.manageTaxInformationComponent.getTaxInformation();
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { BillingHistoryResponse } from "@bitwarden/common/billing/models/response/billing-history.response";
|
||||||
|
|
||||||
import { OrganizationApiKeyRequest } from "../../../admin-console/models/request/organization-api-key.request";
|
import { OrganizationApiKeyRequest } from "../../../admin-console/models/request/organization-api-key.request";
|
||||||
import { OrganizationSsoRequest } from "../../../auth/models/request/organization-sso.request";
|
import { OrganizationSsoRequest } from "../../../auth/models/request/organization-sso.request";
|
||||||
import { SecretVerificationRequest } from "../../../auth/models/request/secret-verification.request";
|
import { SecretVerificationRequest } from "../../../auth/models/request/secret-verification.request";
|
||||||
@@ -33,6 +35,7 @@ import { ProfileOrganizationResponse } from "../../models/response/profile-organ
|
|||||||
export class OrganizationApiServiceAbstraction {
|
export class OrganizationApiServiceAbstraction {
|
||||||
get: (id: string) => Promise<OrganizationResponse>;
|
get: (id: string) => Promise<OrganizationResponse>;
|
||||||
getBilling: (id: string) => Promise<BillingResponse>;
|
getBilling: (id: string) => Promise<BillingResponse>;
|
||||||
|
getBillingHistory: (id: string) => Promise<BillingHistoryResponse>;
|
||||||
getSubscription: (id: string) => Promise<OrganizationSubscriptionResponse>;
|
getSubscription: (id: string) => Promise<OrganizationSubscriptionResponse>;
|
||||||
getLicense: (id: string, installationId: string) => Promise<unknown>;
|
getLicense: (id: string, installationId: string) => Promise<unknown>;
|
||||||
getAutoEnrollStatus: (identifier: string) => Promise<OrganizationAutoEnrollStatusResponse>;
|
getAutoEnrollStatus: (identifier: string) => Promise<OrganizationAutoEnrollStatusResponse>;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { BillingHistoryResponse } from "@bitwarden/common/billing/models/response/billing-history.response";
|
||||||
|
|
||||||
import { ApiService } from "../../../abstractions/api.service";
|
import { ApiService } from "../../../abstractions/api.service";
|
||||||
import { OrganizationApiKeyRequest } from "../../../admin-console/models/request/organization-api-key.request";
|
import { OrganizationApiKeyRequest } from "../../../admin-console/models/request/organization-api-key.request";
|
||||||
import { OrganizationSsoRequest } from "../../../auth/models/request/organization-sso.request";
|
import { OrganizationSsoRequest } from "../../../auth/models/request/organization-sso.request";
|
||||||
@@ -55,6 +57,17 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction
|
|||||||
return new BillingResponse(r);
|
return new BillingResponse(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getBillingHistory(id: string): Promise<BillingHistoryResponse> {
|
||||||
|
const r = await this.apiService.send(
|
||||||
|
"GET",
|
||||||
|
"/organizations/" + id + "/billing/history",
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return new BillingHistoryResponse(r);
|
||||||
|
}
|
||||||
|
|
||||||
async getSubscription(id: string): Promise<OrganizationSubscriptionResponse> {
|
async getSubscription(id: string): Promise<OrganizationSubscriptionResponse> {
|
||||||
const r = await this.apiService.send(
|
const r = await this.apiService.send(
|
||||||
"GET",
|
"GET",
|
||||||
|
|||||||
@@ -4,26 +4,12 @@ import { PaymentMethodType, TransactionType } from "../../enums";
|
|||||||
export class BillingResponse extends BaseResponse {
|
export class BillingResponse extends BaseResponse {
|
||||||
balance: number;
|
balance: number;
|
||||||
paymentSource: BillingSourceResponse;
|
paymentSource: BillingSourceResponse;
|
||||||
invoices: BillingInvoiceResponse[] = [];
|
|
||||||
transactions: BillingTransactionResponse[] = [];
|
|
||||||
|
|
||||||
constructor(response: any) {
|
constructor(response: any) {
|
||||||
super(response);
|
super(response);
|
||||||
this.balance = this.getResponseProperty("Balance");
|
this.balance = this.getResponseProperty("Balance");
|
||||||
const paymentSource = this.getResponseProperty("PaymentSource");
|
const paymentSource = this.getResponseProperty("PaymentSource");
|
||||||
const transactions = this.getResponseProperty("Transactions");
|
|
||||||
const invoices = this.getResponseProperty("Invoices");
|
|
||||||
this.paymentSource = paymentSource == null ? null : new BillingSourceResponse(paymentSource);
|
this.paymentSource = paymentSource == null ? null : new BillingSourceResponse(paymentSource);
|
||||||
if (transactions != null) {
|
|
||||||
this.transactions = transactions.map((t: any) => new BillingTransactionResponse(t));
|
|
||||||
}
|
|
||||||
if (invoices != null) {
|
|
||||||
this.invoices = invoices.map((i: any) => new BillingInvoiceResponse(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get hasNoHistory() {
|
|
||||||
return this.invoices.length == 0 && this.transactions.length == 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,10 +51,18 @@ export class BadgeDirective implements FocusableElement {
|
|||||||
.concat(this.hasHoverEffects ? hoverStyles[this.variant] : [])
|
.concat(this.hasHoverEffects ? hoverStyles[this.variant] : [])
|
||||||
.concat(this.truncate ? ["tw-truncate", this.maxWidthClass] : []);
|
.concat(this.truncate ? ["tw-truncate", this.maxWidthClass] : []);
|
||||||
}
|
}
|
||||||
@HostBinding("attr.title") get title() {
|
@HostBinding("attr.title") get titleAttr() {
|
||||||
|
if (this.title !== undefined) {
|
||||||
|
return this.title;
|
||||||
|
}
|
||||||
return this.truncate ? this.el.nativeElement.textContent.trim() : null;
|
return this.truncate ? this.el.nativeElement.textContent.trim() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional override for the automatic badge title when truncating.
|
||||||
|
*/
|
||||||
|
@Input() title?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variant, sets the background color of the badge.
|
* Variant, sets the background color of the badge.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { FocusableOption } from "@angular/cdk/a11y";
|
import { FocusableOption } from "@angular/cdk/a11y";
|
||||||
import { Component, ElementRef, HostBinding } from "@angular/core";
|
import { coerceBooleanProperty } from "@angular/cdk/coercion";
|
||||||
|
import { Component, ElementRef, HostBinding, Input } from "@angular/core";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "[bitMenuItem]",
|
selector: "[bitMenuItem]",
|
||||||
@@ -32,6 +33,11 @@ export class MenuItemDirective implements FocusableOption {
|
|||||||
];
|
];
|
||||||
@HostBinding("attr.role") role = "menuitem";
|
@HostBinding("attr.role") role = "menuitem";
|
||||||
@HostBinding("tabIndex") tabIndex = "-1";
|
@HostBinding("tabIndex") tabIndex = "-1";
|
||||||
|
@HostBinding("attr.disabled") get disabledAttr() {
|
||||||
|
return this.disabled || null; // native disabled attr must be null when false
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input({ transform: coerceBooleanProperty }) disabled?: boolean = false;
|
||||||
|
|
||||||
constructor(private elementRef: ElementRef) {}
|
constructor(private elementRef: ElementRef) {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Directive, HostBinding, HostListener, Input, OnChanges } from "@angular/core";
|
import { Directive, HostBinding, HostListener, Input, OnChanges, Optional } from "@angular/core";
|
||||||
|
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
|
import { MenuItemDirective } from "@bitwarden/components";
|
||||||
import { CopyAction, CopyCipherFieldService } from "@bitwarden/vault";
|
import { CopyAction, CopyCipherFieldService } from "@bitwarden/vault";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -9,6 +10,8 @@ import { CopyAction, CopyCipherFieldService } from "@bitwarden/vault";
|
|||||||
*
|
*
|
||||||
* Automatically disables the host element if the field to copy is not available or null.
|
* Automatically disables the host element if the field to copy is not available or null.
|
||||||
*
|
*
|
||||||
|
* If the host element is a menu item, it will be hidden when disabled.
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```html
|
* ```html
|
||||||
* <button appCopyField="username" [cipher]="cipher">Copy Username</button>
|
* <button appCopyField="username" [cipher]="cipher">Copy Username</button>
|
||||||
@@ -27,11 +30,23 @@ export class CopyCipherFieldDirective implements OnChanges {
|
|||||||
|
|
||||||
@Input({ required: true }) cipher: CipherView;
|
@Input({ required: true }) cipher: CipherView;
|
||||||
|
|
||||||
constructor(private copyCipherFieldService: CopyCipherFieldService) {}
|
constructor(
|
||||||
|
private copyCipherFieldService: CopyCipherFieldService,
|
||||||
|
@Optional() private menuItemDirective?: MenuItemDirective,
|
||||||
|
) {}
|
||||||
|
|
||||||
@HostBinding("attr.disabled")
|
@HostBinding("attr.disabled")
|
||||||
protected disabled: boolean | null = null;
|
protected disabled: boolean | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the element if it is disabled and is a menu item.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
@HostBinding("class.tw-hidden")
|
||||||
|
private get hidden() {
|
||||||
|
return this.disabled && this.menuItemDirective;
|
||||||
|
}
|
||||||
|
|
||||||
@HostListener("click")
|
@HostListener("click")
|
||||||
async copy() {
|
async copy() {
|
||||||
const value = this.getValueToCopy();
|
const value = this.getValueToCopy();
|
||||||
@@ -49,6 +64,11 @@ export class CopyCipherFieldDirective implements OnChanges {
|
|||||||
(this.action === "totp" && !(await this.copyCipherFieldService.totpAllowed(this.cipher)))
|
(this.action === "totp" && !(await this.copyCipherFieldService.totpAllowed(this.cipher)))
|
||||||
? true
|
? true
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
// If the directive is used on a menu item, update the menu item to prevent keyboard navigation
|
||||||
|
if (this.menuItemDirective) {
|
||||||
|
this.menuItemDirective.disabled = this.disabled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getValueToCopy() {
|
private getValueToCopy() {
|
||||||
|
|||||||
74
package-lock.json
generated
74
package-lock.json
generated
@@ -173,7 +173,7 @@
|
|||||||
"remark-gfm": "3.0.1",
|
"remark-gfm": "3.0.1",
|
||||||
"rimraf": "5.0.7",
|
"rimraf": "5.0.7",
|
||||||
"sass": "1.74.1",
|
"sass": "1.74.1",
|
||||||
"sass-loader": "13.3.3",
|
"sass-loader": "14.2.1",
|
||||||
"storybook": "7.6.19",
|
"storybook": "7.6.19",
|
||||||
"style-loader": "3.3.4",
|
"style-loader": "3.3.4",
|
||||||
"tailwindcss": "3.4.3",
|
"tailwindcss": "3.4.3",
|
||||||
@@ -185,7 +185,7 @@
|
|||||||
"url": "0.11.3",
|
"url": "0.11.3",
|
||||||
"util": "0.12.5",
|
"util": "0.12.5",
|
||||||
"wait-on": "7.2.0",
|
"wait-on": "7.2.0",
|
||||||
"webpack": "5.89.0",
|
"webpack": "5.92.0",
|
||||||
"webpack-cli": "5.1.4",
|
"webpack-cli": "5.1.4",
|
||||||
"webpack-dev-server": "5.0.4",
|
"webpack-dev-server": "5.0.4",
|
||||||
"webpack-node-externals": "3.0.0"
|
"webpack-node-externals": "3.0.0"
|
||||||
@@ -237,7 +237,7 @@
|
|||||||
},
|
},
|
||||||
"apps/desktop": {
|
"apps/desktop": {
|
||||||
"name": "@bitwarden/desktop",
|
"name": "@bitwarden/desktop",
|
||||||
"version": "2024.6.0",
|
"version": "2024.6.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "GPL-3.0"
|
"license": "GPL-3.0"
|
||||||
},
|
},
|
||||||
@@ -13528,6 +13528,16 @@
|
|||||||
"acorn": "^8"
|
"acorn": "^8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/acorn-import-attributes": {
|
||||||
|
"version": "1.9.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz",
|
||||||
|
"integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"acorn": "^8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/acorn-jsx": {
|
"node_modules/acorn-jsx": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
|
||||||
@@ -18836,10 +18846,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/enhanced-resolve": {
|
"node_modules/enhanced-resolve": {
|
||||||
"version": "5.16.0",
|
"version": "5.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz",
|
||||||
"integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==",
|
"integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"graceful-fs": "^4.2.4",
|
"graceful-fs": "^4.2.4",
|
||||||
"tapable": "^2.2.0"
|
"tapable": "^2.2.0"
|
||||||
@@ -34924,29 +34935,30 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sass-loader": {
|
"node_modules/sass-loader": {
|
||||||
"version": "13.3.3",
|
"version": "14.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.2.1.tgz",
|
||||||
"integrity": "sha512-mt5YN2F1MOZr3d/wBRcZxeFgwgkH44wVc2zohO2YF6JiOMkiXe4BYRZpSu2sO1g71mo/j16txzUhsKZlqjVGzA==",
|
"integrity": "sha512-G0VcnMYU18a4N7VoNDegg2OuMjYtxnqzQWARVWCIVSZwJeiL9kg8QMsuIZOplsJgTzZLF6jGxI3AClj8I9nRdQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"neo-async": "^2.6.2"
|
"neo-async": "^2.6.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 14.15.0"
|
"node": ">= 18.12.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/webpack"
|
"url": "https://opencollective.com/webpack"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"fibers": ">= 3.1.0",
|
"@rspack/core": "0.x || 1.x",
|
||||||
"node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0",
|
"node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0",
|
||||||
"sass": "^1.3.0",
|
"sass": "^1.3.0",
|
||||||
"sass-embedded": "*",
|
"sass-embedded": "*",
|
||||||
"webpack": "^5.0.0"
|
"webpack": "^5.0.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"fibers": {
|
"@rspack/core": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"node-sass": {
|
"node-sass": {
|
||||||
@@ -34957,6 +34969,9 @@
|
|||||||
},
|
},
|
||||||
"sass-embedded": {
|
"sass-embedded": {
|
||||||
"optional": true
|
"optional": true
|
||||||
|
},
|
||||||
|
"webpack": {
|
||||||
|
"optional": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -39429,34 +39444,35 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/webpack": {
|
"node_modules/webpack": {
|
||||||
"version": "5.89.0",
|
"version": "5.92.0",
|
||||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz",
|
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.92.0.tgz",
|
||||||
"integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==",
|
"integrity": "sha512-Bsw2X39MYIgxouNATyVpCNVWBCuUwDgWtN78g6lSdPJRLaQ/PUVm/oXcaRAyY/sMFoKFQrsPeqvTizWtq7QPCA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/eslint-scope": "^3.7.3",
|
"@types/eslint-scope": "^3.7.3",
|
||||||
"@types/estree": "^1.0.0",
|
"@types/estree": "^1.0.5",
|
||||||
"@webassemblyjs/ast": "^1.11.5",
|
"@webassemblyjs/ast": "^1.12.1",
|
||||||
"@webassemblyjs/wasm-edit": "^1.11.5",
|
"@webassemblyjs/wasm-edit": "^1.12.1",
|
||||||
"@webassemblyjs/wasm-parser": "^1.11.5",
|
"@webassemblyjs/wasm-parser": "^1.12.1",
|
||||||
"acorn": "^8.7.1",
|
"acorn": "^8.7.1",
|
||||||
"acorn-import-assertions": "^1.9.0",
|
"acorn-import-attributes": "^1.9.5",
|
||||||
"browserslist": "^4.14.5",
|
"browserslist": "^4.21.10",
|
||||||
"chrome-trace-event": "^1.0.2",
|
"chrome-trace-event": "^1.0.2",
|
||||||
"enhanced-resolve": "^5.15.0",
|
"enhanced-resolve": "^5.17.0",
|
||||||
"es-module-lexer": "^1.2.1",
|
"es-module-lexer": "^1.2.1",
|
||||||
"eslint-scope": "5.1.1",
|
"eslint-scope": "5.1.1",
|
||||||
"events": "^3.2.0",
|
"events": "^3.2.0",
|
||||||
"glob-to-regexp": "^0.4.1",
|
"glob-to-regexp": "^0.4.1",
|
||||||
"graceful-fs": "^4.2.9",
|
"graceful-fs": "^4.2.11",
|
||||||
"json-parse-even-better-errors": "^2.3.1",
|
"json-parse-even-better-errors": "^2.3.1",
|
||||||
"loader-runner": "^4.2.0",
|
"loader-runner": "^4.2.0",
|
||||||
"mime-types": "^2.1.27",
|
"mime-types": "^2.1.27",
|
||||||
"neo-async": "^2.6.2",
|
"neo-async": "^2.6.2",
|
||||||
"schema-utils": "^3.2.0",
|
"schema-utils": "^3.2.0",
|
||||||
"tapable": "^2.1.1",
|
"tapable": "^2.1.1",
|
||||||
"terser-webpack-plugin": "^5.3.7",
|
"terser-webpack-plugin": "^5.3.10",
|
||||||
"watchpack": "^2.4.0",
|
"watchpack": "^2.4.1",
|
||||||
"webpack-sources": "^3.2.3"
|
"webpack-sources": "^3.2.3"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -39834,6 +39850,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
"fast-json-stable-stringify": "^2.0.0",
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
@@ -39850,6 +39867,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
||||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"ajv": "^6.9.1"
|
"ajv": "^6.9.1"
|
||||||
}
|
}
|
||||||
@@ -39859,6 +39877,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||||
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
|
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esrecurse": "^4.3.0",
|
"esrecurse": "^4.3.0",
|
||||||
"estraverse": "^4.1.1"
|
"estraverse": "^4.1.1"
|
||||||
@@ -39872,6 +39891,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
|
||||||
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
|
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.0"
|
"node": ">=4.0"
|
||||||
}
|
}
|
||||||
@@ -39880,13 +39900,15 @@
|
|||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/webpack/node_modules/schema-utils": {
|
"node_modules/webpack/node_modules/schema-utils": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
|
||||||
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
|
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/json-schema": "^7.0.8",
|
"@types/json-schema": "^7.0.8",
|
||||||
"ajv": "^6.12.5",
|
"ajv": "^6.12.5",
|
||||||
|
|||||||
@@ -134,7 +134,7 @@
|
|||||||
"remark-gfm": "3.0.1",
|
"remark-gfm": "3.0.1",
|
||||||
"rimraf": "5.0.7",
|
"rimraf": "5.0.7",
|
||||||
"sass": "1.74.1",
|
"sass": "1.74.1",
|
||||||
"sass-loader": "13.3.3",
|
"sass-loader": "14.2.1",
|
||||||
"storybook": "7.6.19",
|
"storybook": "7.6.19",
|
||||||
"style-loader": "3.3.4",
|
"style-loader": "3.3.4",
|
||||||
"tailwindcss": "3.4.3",
|
"tailwindcss": "3.4.3",
|
||||||
@@ -146,7 +146,7 @@
|
|||||||
"url": "0.11.3",
|
"url": "0.11.3",
|
||||||
"util": "0.12.5",
|
"util": "0.12.5",
|
||||||
"wait-on": "7.2.0",
|
"wait-on": "7.2.0",
|
||||||
"webpack": "5.89.0",
|
"webpack": "5.92.0",
|
||||||
"webpack-cli": "5.1.4",
|
"webpack-cli": "5.1.4",
|
||||||
"webpack-dev-server": "5.0.4",
|
"webpack-dev-server": "5.0.4",
|
||||||
"webpack-node-externals": "3.0.0"
|
"webpack-node-externals": "3.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user