1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-12 06:23:38 +00:00

Merge branch 'main' into tools/PM-14927/display-critical-app-count

This commit is contained in:
voommen-livefront
2024-12-05 12:28:43 -06:00
116 changed files with 1364 additions and 684 deletions

4
.github/CODEOWNERS vendored
View File

@@ -4,10 +4,6 @@
#
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
## Secrets Manager team files ##
bitwarden_license/bit-web/src/app/secrets-manager @bitwarden/team-secrets-manager-dev
apps/web/src/app/secrets-manager/ @bitwarden/team-secrets-manager-dev
## Auth team files ##
apps/browser/src/auth @bitwarden/team-auth-dev
apps/cli/src/auth @bitwarden/team-auth-dev

View File

@@ -170,7 +170,7 @@ jobs:
- name: Set up environment
run: |
sudo apt-get update
sudo apt-get -y install pkg-config libxss-dev libsecret-1-dev rpm musl-dev musl-tools flatpak flatpak-builder
sudo apt-get -y install pkg-config libxss-dev rpm musl-dev musl-tools flatpak flatpak-builder
- name: Set up Snap
run: sudo snap install snapcraft --classic

View File

@@ -138,7 +138,7 @@ jobs:
- name: Set up environment
run: |
sudo apt-get update
sudo apt-get -y install pkg-config libxss-dev libsecret-1-dev rpm
sudo apt-get -y install pkg-config libxss-dev rpm
- name: Set up Snap
run: sudo snap install snapcraft --classic

View File

@@ -57,6 +57,7 @@ const config: StorybookConfig = {
return config;
},
docs: {},
staticDirs: ["../apps/web/src/images"],
};
export default config;

View File

@@ -438,8 +438,8 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
const successful = await this.trySetupBiometrics();
this.form.controls.biometric.setValue(successful);
await this.biometricStateService.setBiometricUnlockEnabled(successful);
if (!successful) {
await this.biometricStateService.setBiometricUnlockEnabled(false);
await this.biometricStateService.setFingerprintValidated(false);
}
} else {

View File

@@ -1,7 +1,7 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header>
<div class="left">
<a (click)="logOut()">{{ "logOut" | i18n }}</a>
<button type="button" (click)="logOut()">{{ "logOut" | i18n }}</button>
</div>
<h1 class="center">
<span class="title">{{ "updateMasterPassword" | i18n }}</span>

View File

@@ -362,8 +362,6 @@ export class OverlayBackground implements OverlayBackgroundInterface {
this.inlineMenuFido2Credentials.clear();
this.storeInlineMenuFido2Credentials$.next(currentTab.id);
await this.generatePassword();
const ciphersViews = await this.getCipherViews(currentTab, updateAllCipherTypes);
for (let cipherIndex = 0; cipherIndex < ciphersViews.length; cipherIndex++) {
this.inlineMenuCiphers.set(`inline-menu-cipher-${cipherIndex}`, ciphersViews[cipherIndex]);

View File

@@ -49,7 +49,7 @@ export class FilePopoutUtilsService {
}
/**
* Determines whether to show a file popout callout message for Chromium-based browsers in Linux and Mac OS X Big Sur
* Determines whether to show a file popout callout message for Chromium-based browsers in Linux and Mac OS X
* @param win - The window context in which the check should be performed.
* @returns True if the extension is not in a sidebar or popout; otherwise, false.
*/
@@ -66,8 +66,6 @@ export class FilePopoutUtilsService {
}
private isUnsupportedMac(win: Window): boolean {
return (
this.platformUtilsService.isChrome() && win?.navigator?.appVersion.includes("Mac OS X 11")
);
return this.platformUtilsService.isChrome() && win?.navigator?.appVersion.includes("Mac OS X");
}
}

View File

@@ -118,6 +118,7 @@
type="button"
class="box-content-row"
appStopClick
*ngIf="isSshKeysEnabled"
(click)="selectType(cipherType.SshKey)"
>
<div class="row-main">

View File

@@ -7,7 +7,9 @@ import { first, switchMap, takeUntil } from "rxjs/operators";
import { CollectionView } from "@bitwarden/admin-console/common";
import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SyncService } from "@bitwarden/common/platform/sync";
@@ -62,6 +64,8 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
selectedOrganization: string = null;
showCollections = true;
isSshKeysEnabled = false;
private loadedTimeout: number;
private selectedTimeout: number;
private preventSelected = false;
@@ -95,6 +99,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
private location: Location,
private vaultFilterService: VaultFilterService,
private vaultBrowserStateService: VaultBrowserStateService,
private configService: ConfigService,
) {
this.noFolderListSize = 100;
}
@@ -166,6 +171,8 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
.subscribe((isSearchable) => {
this.isSearchable = isSearchable;
});
this.isSshKeysEnabled = await this.configService.getFeatureFlag(FeatureFlag.SSHKeyVaultItem);
}
ngOnDestroy() {

View File

@@ -30,7 +30,7 @@
<button type="button" bitMenuItem (click)="restore(cipher)">
{{ "restore" | i18n }}
</button>
<button type="button" bitMenuItem (click)="delete(cipher)">
<button type="button" bitMenuItem *appCanDeleteCipher="cipher" (click)="delete(cipher)">
{{ "deleteForever" | i18n }}
</button>
</bit-menu>

View File

@@ -1,5 +1,5 @@
import { CommonModule } from "@angular/common";
import { Component, Input } from "@angular/core";
import { ChangeDetectionStrategy, Component, Input } from "@angular/core";
import { Router } from "@angular/router";
import { JslibModule } from "@bitwarden/angular/jslib.module";
@@ -17,7 +17,7 @@ import {
ToastService,
TypographyModule,
} from "@bitwarden/components";
import { PasswordRepromptService } from "@bitwarden/vault";
import { CanDeleteCipherDirective, PasswordRepromptService } from "@bitwarden/vault";
@Component({
selector: "app-trash-list-items-container",
@@ -29,10 +29,12 @@ import { PasswordRepromptService } from "@bitwarden/vault";
JslibModule,
SectionComponent,
SectionHeaderComponent,
CanDeleteCipherDirective,
MenuModule,
IconButtonModule,
TypographyModule,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TrashListItemsContainerComponent {
/**

View File

@@ -1,5 +1,5 @@
import { CommonModule } from "@angular/common";
import { Component } from "@angular/core";
import { ChangeDetectionStrategy, Component } from "@angular/core";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { CalloutModule, NoItemsModule } from "@bitwarden/components";
@@ -27,6 +27,7 @@ import { TrashListItemsContainerComponent } from "./trash-list-items-container/t
CalloutModule,
NoItemsModule,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TrashComponent {
protected deletedCiphers$ = this.vaultPopupItemsService.deletedCiphers$;

View File

@@ -36,6 +36,7 @@ dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
"zeroize",
]
[[package]]
@@ -83,6 +84,19 @@ dependencies = [
"x11rb",
]
[[package]]
name = "argon2"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
dependencies = [
"base64ct",
"blake2",
"cpufeatures",
"password-hash",
"zeroize",
]
[[package]]
name = "async-broadcast"
version = "0.7.1"
@@ -161,6 +175,17 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "async-net"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7"
dependencies = [
"async-io",
"blocking",
"futures-lite",
]
[[package]]
name = "async-process"
version = "2.3.0"
@@ -320,6 +345,15 @@ dependencies = [
"tokio-util",
]
[[package]]
name = "blake2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
"digest",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
@@ -400,16 +434,6 @@ dependencies = [
"shlex",
]
[[package]]
name = "cfg-expr"
version = "0.15.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02"
dependencies = [
"smallvec",
"target-lexicon",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
@@ -447,6 +471,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
"zeroize",
]
[[package]]
@@ -530,6 +555,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"rand_core",
"typenum",
]
@@ -660,6 +686,7 @@ dependencies = [
"aes",
"anyhow",
"arboard",
"argon2",
"async-stream",
"base64",
"bitwarden-russh",
@@ -669,13 +696,12 @@ dependencies = [
"dirs",
"ed25519",
"futures",
"gio",
"homedir",
"interprocess",
"keytar",
"libc",
"libsecret",
"log",
"oo7",
"pin-project",
"pkcs8",
"rand",
@@ -1056,105 +1082,12 @@ version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "gio"
version = "0.19.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be548be810e45dd31d3bbb89c6210980bb7af9bca3ea1292b5f16b75f8e394a7"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-util",
"gio-sys",
"glib",
"libc",
"pin-project-lite",
"smallvec",
"thiserror",
]
[[package]]
name = "gio-sys"
version = "0.19.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cd743ba4714d671ad6b6234e8ab2a13b42304d0e13ab7eba1dcdd78a7d6d4ef"
dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
"windows-sys 0.52.0",
]
[[package]]
name = "glib"
version = "0.19.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39650279f135469465018daae0ba53357942a5212137515777d5fdca74984a44"
dependencies = [
"bitflags",
"futures-channel",
"futures-core",
"futures-executor",
"futures-task",
"futures-util",
"gio-sys",
"glib-macros",
"glib-sys",
"gobject-sys",
"libc",
"memchr",
"smallvec",
"thiserror",
]
[[package]]
name = "glib-macros"
version = "0.19.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4429b0277a14ae9751350ad9b658b1be0abb5b54faa5bcdf6e74a3372582fad7"
dependencies = [
"heck",
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "glib-sys"
version = "0.19.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c2dc18d3a82b0006d470b13304fbbb3e0a9bd4884cf985a60a7ed733ac2c4a5"
dependencies = [
"libc",
"system-deps",
]
[[package]]
name = "gobject-sys"
version = "0.19.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e697e252d6e0416fd1d9e169bda51c0f1c926026c39ca21fbe8b1bb5c3b8b9e"
dependencies = [
"glib-sys",
"libc",
"system-deps",
]
[[package]]
name = "hashbrown"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.3.9"
@@ -1173,6 +1106,15 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hkdf"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
dependencies = [
"hmac",
]
[[package]]
name = "hmac"
version = "0.12.1"
@@ -1283,9 +1225,9 @@ dependencies = [
[[package]]
name = "libm"
version = "0.2.8"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
[[package]]
name = "libredox"
@@ -1297,32 +1239,6 @@ dependencies = [
"libc",
]
[[package]]
name = "libsecret"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50c6ccddc706a38eca477b4d7857acd6c76c7d6fba5d47b4b2e7d800e5a17194"
dependencies = [
"gio",
"glib",
"libc",
"libsecret-sys",
]
[[package]]
name = "libsecret-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a1af48e61f1c8e77e9705296f346e45b637754a92348a79b4c62df84d0654c2"
dependencies = [
"gio-sys",
"glib-sys",
"gobject-sys",
"libc",
"pkg-config",
"system-deps",
]
[[package]]
name = "link-cplusplus"
version = "1.0.9"
@@ -1354,6 +1270,16 @@ version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "md-5"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [
"cfg-if",
"digest",
]
[[package]]
name = "memchr"
version = "2.7.4"
@@ -1418,9 +1344,9 @@ checksum = "e1c0f5d67ee408a4685b61f5ab7e58605c8ae3f2b4189f0127d804ff13d5560a"
[[package]]
name = "napi-derive"
version = "2.16.12"
version = "2.16.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17435f7a00bfdab20b0c27d9c56f58f6499e418252253081bfff448099da31d1"
checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c"
dependencies = [
"cfg-if",
"convert_case",
@@ -1432,9 +1358,9 @@ dependencies = [
[[package]]
name = "napi-derive-backend"
version = "1.0.74"
version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "967c485e00f0bf3b1bdbe510a38a4606919cf1d34d9a37ad41f25a81aa077abe"
checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf"
dependencies = [
"convert_case",
"once_cell",
@@ -1489,6 +1415,30 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "num"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-bigint-dig"
version = "0.8.4"
@@ -1502,10 +1452,20 @@ dependencies = [
"num-iter",
"num-traits",
"rand",
"serde",
"smallvec",
"zeroize",
]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
]
[[package]]
name = "num-conv"
version = "0.1.0"
@@ -1532,6 +1492,17 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -1665,6 +1636,39 @@ version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "oo7"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc6ce4692fbfd044ce22ca07dcab1a30fa12432ca2aa5b1294eca50d3332a24"
dependencies = [
"aes",
"async-fs",
"async-io",
"async-lock",
"async-net",
"blocking",
"cbc",
"cipher",
"digest",
"endi",
"futures-lite",
"futures-util",
"hkdf",
"hmac",
"md-5",
"num",
"num-bigint-dig",
"pbkdf2",
"rand",
"serde",
"sha2",
"subtle",
"zbus",
"zeroize",
"zvariant",
]
[[package]]
name = "opaque-debug"
version = "0.3.1"
@@ -1726,6 +1730,17 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "password-hash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
dependencies = [
"base64ct",
"rand_core",
"subtle",
]
[[package]]
name = "pbkdf2"
version = "0.12.2"
@@ -2182,15 +2197,6 @@ dependencies = [
"syn",
]
[[package]]
name = "serde_spanned"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
dependencies = [
"serde",
]
[[package]]
name = "sha1"
version = "0.10.6"
@@ -2360,25 +2366,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "system-deps"
version = "6.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349"
dependencies = [
"cfg-expr",
"heck",
"pkg-config",
"toml",
"version-compare",
]
[[package]]
name = "target-lexicon"
version = "0.12.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tempfile"
version = "3.13.0"
@@ -2505,26 +2492,11 @@ dependencies = [
"tokio",
]
[[package]]
name = "toml"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
@@ -2533,8 +2505,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
@@ -2628,12 +2598,6 @@ dependencies = [
"subtle",
]
[[package]]
name = "version-compare"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b"
[[package]]
name = "version_check"
version = "0.9.5"
@@ -3198,6 +3162,20 @@ name = "zeroize"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
dependencies = [
"zeroize_derive",
]
[[package]]
name = "zeroize_derive"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zvariant"

View File

@@ -10,15 +10,13 @@ default = ["sys"]
manual_test = []
sys = [
"dep:widestring",
"dep:windows",
"dep:core-foundation",
"dep:security-framework",
"dep:security-framework-sys",
"dep:gio",
"dep:libsecret",
"dep:zbus",
"dep:zbus_polkit",
"dep:widestring",
"dep:windows",
"dep:core-foundation",
"dep:security-framework",
"dep:security-framework-sys",
"dep:zbus",
"dep:zbus_polkit",
]
[dependencies]
@@ -27,6 +25,7 @@ anyhow = "=1.0.93"
arboard = { version = "=3.4.1", default-features = false, features = [
"wayland-data-control",
] }
argon2 = { version = "=0.5.3", features = ["zeroize"] }
async-stream = "=0.3.6"
base64 = "=0.22.1"
byteorder = "=1.5.0"
@@ -84,7 +83,7 @@ security-framework = { version = "=3.0.0", optional = true }
security-framework-sys = { version = "=2.12.0", optional = true }
[target.'cfg(target_os = "linux")'.dependencies]
gio = { version = "=0.19.5", optional = true }
libsecret = { version = "=0.5.0", optional = true }
oo7 = "=0.3.3"
zbus = { version = "=4.4.0", optional = true }
zbus_polkit = { version = "=4.0.0", optional = true }

View File

@@ -18,7 +18,7 @@ impl super::BiometricTrait for Biometric {
bail!("platform not supported");
}
fn get_biometric_secret(
async fn get_biometric_secret(
_service: &str,
_account: &str,
_key_material: Option<KeyMaterial>,
@@ -26,7 +26,7 @@ impl super::BiometricTrait for Biometric {
bail!("platform not supported");
}
fn set_biometric_secret(
async fn set_biometric_secret(
_service: &str,
_account: &str,
_secret: &str,

View File

@@ -22,20 +22,19 @@ pub struct OsDerivedKey {
pub iv_b64: String,
}
#[allow(async_fn_in_trait)]
pub trait BiometricTrait {
#[allow(async_fn_in_trait)]
async fn prompt(hwnd: Vec<u8>, message: String) -> Result<bool>;
#[allow(async_fn_in_trait)]
async fn available() -> Result<bool>;
fn derive_key_material(secret: Option<&str>) -> Result<OsDerivedKey>;
fn set_biometric_secret(
async fn set_biometric_secret(
service: &str,
account: &str,
secret: &str,
key_material: Option<KeyMaterial>,
iv_b64: &str,
) -> Result<String>;
fn get_biometric_secret(
async fn get_biometric_secret(
service: &str,
account: &str,
key_material: Option<KeyMaterial>,

View File

@@ -73,7 +73,7 @@ impl super::BiometricTrait for Biometric {
Ok(OsDerivedKey { key_b64, iv_b64 })
}
fn set_biometric_secret(
async fn set_biometric_secret(
service: &str,
account: &str,
secret: &str,
@@ -85,11 +85,11 @@ impl super::BiometricTrait for Biometric {
))?;
let encrypted_secret = encrypt(secret, &key_material, iv_b64)?;
crate::password::set_password(service, account, &encrypted_secret)?;
crate::password::set_password(service, account, &encrypted_secret).await?;
Ok(encrypted_secret)
}
fn get_biometric_secret(
async fn get_biometric_secret(
service: &str,
account: &str,
key_material: Option<KeyMaterial>,
@@ -98,7 +98,7 @@ impl super::BiometricTrait for Biometric {
"Key material is required for polkit protected keys"
))?;
let encrypted_secret = crate::password::get_password(service, account)?;
let encrypted_secret = crate::password::get_password(service, account).await?;
let secret = CipherString::from_str(&encrypted_secret)?;
return Ok(decrypt(&secret, &key_material)?);
}

View File

@@ -121,7 +121,7 @@ impl super::BiometricTrait for Biometric {
Ok(OsDerivedKey { key_b64, iv_b64 })
}
fn set_biometric_secret(
async fn set_biometric_secret(
service: &str,
account: &str,
secret: &str,
@@ -133,11 +133,11 @@ impl super::BiometricTrait for Biometric {
))?;
let encrypted_secret = encrypt(secret, &key_material, iv_b64)?;
crate::password::set_password(service, account, &encrypted_secret)?;
crate::password::set_password(service, account, &encrypted_secret).await?;
Ok(encrypted_secret)
}
fn get_biometric_secret(
async fn get_biometric_secret(
service: &str,
account: &str,
key_material: Option<KeyMaterial>,
@@ -146,7 +146,7 @@ impl super::BiometricTrait for Biometric {
"Key material is required for Windows Hello protected keys"
))?;
let encrypted_secret = crate::password::get_password(service, account)?;
let encrypted_secret = crate::password::get_password(service, account).await?;
match CipherString::from_str(&encrypted_secret) {
Ok(secret) => {
// If the secret is a CipherString, it is encrypted and we need to decrypt it.
@@ -292,9 +292,9 @@ mod tests {
assert_eq!(decrypt(&secret, &key_material).unwrap(), "secret")
}
#[test]
fn get_biometric_secret_requires_key() {
let result = <Biometric as BiometricTrait>::get_biometric_secret("", "", None);
#[tokio::test]
async fn get_biometric_secret_requires_key() {
let result = <Biometric as BiometricTrait>::get_biometric_secret("", "", None).await;
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
@@ -302,29 +302,25 @@ mod tests {
);
}
#[test]
fn get_biometric_secret_handles_unencrypted_secret() {
scopeguard::defer! {
crate::password::delete_password("test", "test").unwrap();
}
#[tokio::test]
async fn get_biometric_secret_handles_unencrypted_secret() {
let test = "test";
let secret = "password";
let key_material = KeyMaterial {
os_key_part_b64: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned(),
client_key_part_b64: Some("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned()),
};
crate::password::set_password(test, test, secret).unwrap();
crate::password::set_password(test, test, secret).await.unwrap();
let result =
<Biometric as BiometricTrait>::get_biometric_secret(test, test, Some(key_material))
.await
.unwrap();
crate::password::delete_password("test", "test").await.unwrap();
assert_eq!(result, secret);
}
#[test]
fn get_biometric_secret_handles_encrypted_secret() {
scopeguard::defer! {
crate::password::delete_password("test", "test").unwrap();
}
#[tokio::test]
async fn get_biometric_secret_handles_encrypted_secret() {
let test = "test";
let secret =
CipherString::from_str("0.l9fhDUP/wDJcKwmEzcb/3w==|uP4LcqoCCj5FxBDP77NV6Q==").unwrap(); // output from test_encrypt
@@ -332,17 +328,19 @@ mod tests {
os_key_part_b64: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned(),
client_key_part_b64: Some("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned()),
};
crate::password::set_password(test, test, &secret.to_string()).unwrap();
crate::password::set_password(test, test, &secret.to_string()).await.unwrap();
let result =
<Biometric as BiometricTrait>::get_biometric_secret(test, test, Some(key_material))
.await
.unwrap();
crate::password::delete_password("test", "test").await.unwrap();
assert_eq!(result, "secret");
}
#[test]
fn set_biometric_secret_requires_key() {
let result = <Biometric as BiometricTrait>::set_biometric_secret("", "", "", None, "");
#[tokio::test]
async fn set_biometric_secret_requires_key() {
let result = <Biometric as BiometricTrait>::set_biometric_secret("", "", "", None, "").await;
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),

View File

@@ -5,7 +5,7 @@ use aes::cipher::{
BlockEncryptMut, KeyIvInit,
};
use crate::error::{CryptoError, Result};
use crate::error::{CryptoError, KdfParamError, Result};
use super::CipherString;
@@ -37,3 +37,53 @@ pub fn encrypt_aes256(
Ok(CipherString::AesCbc256_B64 { iv, data })
}
pub fn argon2(
secret: &[u8],
salt: &[u8],
iterations: u32,
memory: u32,
parallelism: u32,
) -> Result<[u8; 32]> {
use argon2::*;
let params = Params::new(memory, iterations, parallelism, Some(32)).map_err(|e| {
KdfParamError::InvalidParams(format!("Argon2 parameters are invalid: {e}",))
})?;
let argon = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
let mut hash = [0u8; 32];
argon
.hash_password_into(secret, &salt, &mut hash)
.map_err(|e| KdfParamError::InvalidParams(format!("Argon2 hashing failed: {e}",)))?;
// Argon2 is using some stack memory that is not zeroed. Eventually some function will
// overwrite the stack, but we use this trick to force the used stack to be zeroed.
#[inline(never)]
fn clear_stack() {
std::hint::black_box([0u8; 4096]);
}
clear_stack();
Ok(hash)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_argon2() {
let test_hash: [u8; 32] = [
112, 200, 85, 209, 100, 4, 246, 146, 117, 180, 152, 44, 103, 198, 75, 14, 166, 77, 201,
22, 62, 178, 87, 224, 95, 209, 253, 68, 166, 209, 47, 218,
];
let secret = b"supersecurepassword";
let salt = b"mail@example.com";
let iterations = 3;
let memory = 1024 * 64;
let parallelism = 4;
let hash = argon2(secret, salt, iterations, memory, parallelism).unwrap();
assert_eq!(hash, test_hash,);
}
}

View File

@@ -9,6 +9,8 @@ pub enum Error {
#[error("Cryptography Error, {0}")]
Crypto(#[from] CryptoError),
#[error("KDF Parameter Error, {0}")]
KdfParam(#[from] KdfParamError),
}
#[derive(Debug, Error)]
@@ -29,6 +31,12 @@ pub enum CryptoError {
KeyDecrypt,
}
#[derive(Debug, Error)]
pub enum KdfParamError {
#[error("Invalid KDF parameters: {0}")]
InvalidParams(String),
}
// Ensure that the error messages implement Send and Sync
#[cfg(test)]
const _: () = {

View File

@@ -3,26 +3,22 @@ use security_framework::passwords::{
delete_generic_password, get_generic_password, set_generic_password,
};
pub fn get_password(service: &str, account: &str) -> Result<String> {
pub async fn get_password(service: &str, account: &str) -> Result<String> {
let result = String::from_utf8(get_generic_password(&service, &account)?)?;
Ok(result)
}
pub fn get_password_keytar(service: &str, account: &str) -> Result<String> {
get_password(service, account)
}
pub fn set_password(service: &str, account: &str, password: &str) -> Result<()> {
pub async fn set_password(service: &str, account: &str, password: &str) -> Result<()> {
let result = set_generic_password(&service, &account, password.as_bytes())?;
Ok(result)
}
pub fn delete_password(service: &str, account: &str) -> Result<()> {
pub async fn delete_password(service: &str, account: &str) -> Result<()> {
let result = delete_generic_password(&service, &account)?;
Ok(result)
}
pub fn is_available() -> Result<bool> {
pub async fn is_available() -> Result<bool> {
Ok(true)
}
@@ -30,18 +26,17 @@ pub fn is_available() -> Result<bool> {
mod tests {
use super::*;
#[test]
fn test() {
scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({}););
set_password("BitwardenTest", "BitwardenTest", "Random").unwrap();
#[tokio::test]
async fn test() {
set_password("BitwardenTest", "BitwardenTest", "Random").await.unwrap();
assert_eq!(
"Random",
get_password("BitwardenTest", "BitwardenTest").unwrap()
get_password("BitwardenTest", "BitwardenTest").await.unwrap()
);
delete_password("BitwardenTest", "BitwardenTest").unwrap();
delete_password("BitwardenTest", "BitwardenTest").await.unwrap();
// Ensure password is deleted
match get_password("BitwardenTest", "BitwardenTest") {
match get_password("BitwardenTest", "BitwardenTest").await {
Ok(_) => panic!("Got a result"),
Err(e) => assert_eq!(
"The specified item could not be found in the keychain.",
@@ -50,9 +45,9 @@ mod tests {
}
}
#[test]
fn test_error_no_password() {
match get_password("Unknown", "Unknown") {
#[tokio::test]
async fn test_error_no_password() {
match get_password("Unknown", "Unknown").await {
Ok(_) => panic!("Got a result"),
Err(e) => assert_eq!(
"The specified item could not be found in the keychain.",

View File

@@ -1,106 +1,106 @@
use anyhow::{anyhow, Result};
use libsecret::{password_clear_sync, password_lookup_sync, password_store_sync, Schema};
use oo7::dbus::{self};
use std::collections::HashMap;
pub fn get_password(service: &str, account: &str) -> Result<String> {
let res = password_lookup_sync(
Some(&get_schema()),
build_attributes(service, account),
gio::Cancellable::NONE,
)?;
match res {
Some(s) => Ok(String::from(s)),
None => Err(anyhow!("No password found")),
}
}
pub fn get_password_keytar(service: &str, account: &str) -> Result<String> {
get_password(service, account)
}
pub fn set_password(service: &str, account: &str, password: &str) -> Result<()> {
let result = password_store_sync(
Some(&get_schema()),
build_attributes(service, account),
Some(&libsecret::COLLECTION_DEFAULT),
&format!("{}/{}", service, account),
password,
gio::Cancellable::NONE,
)?;
Ok(result)
}
pub fn delete_password(service: &str, account: &str) -> Result<()> {
let result = password_clear_sync(
Some(&get_schema()),
build_attributes(service, account),
gio::Cancellable::NONE,
)?;
Ok(result)
}
pub fn is_available() -> Result<bool> {
let result = password_clear_sync(
Some(&get_schema()),
build_attributes("bitwardenSecretsAvailabilityTest", "test"),
gio::Cancellable::NONE,
);
match result {
Ok(_) => Ok(true),
pub async fn get_password(service: &str, account: &str) -> Result<String> {
match get_password_new(service, account).await {
Ok(res) => Ok(res),
Err(_) => {
println!("secret-service unavailable: {:?}", result);
Ok(false)
get_password_legacy(service, account).await
}
}
}
fn get_schema() -> Schema {
let mut attributes = std::collections::HashMap::new();
attributes.insert("service", libsecret::SchemaAttributeType::String);
attributes.insert("account", libsecret::SchemaAttributeType::String);
libsecret::Schema::new(
"org.freedesktop.Secret.Generic",
libsecret::SchemaFlags::NONE,
attributes,
)
async fn get_password_new(service: &str, account: &str) -> Result<String> {
let keyring = oo7::Keyring::new().await?;
let attributes = HashMap::from([("service", service), ("account", account)]);
let results = keyring.search_items(&attributes).await?;
let res = results.get(0);
match res {
Some(res) => {
let secret = res.secret().await?;
Ok(String::from_utf8(secret.to_vec())?)
},
None => Err(anyhow!("no result"))
}
}
fn build_attributes<'a>(service: &'a str, account: &'a str) -> HashMap<&'a str, &'a str> {
let mut attributes = HashMap::new();
attributes.insert("service", service);
attributes.insert("account", account);
// forces to read via secret service; remvove after 2025.03
async fn get_password_legacy(service: &str, account: &str) -> Result<String> {
println!("falling back to get legacy {} {}", service, account);
let svc = dbus::Service::new().await?;
let collection = svc.default_collection().await?;
let keyring = oo7::Keyring::DBus(collection);
let attributes = HashMap::from([("service", service), ("account", account)]);
let results = keyring.search_items(&attributes).await?;
let res = results.get(0);
match res {
Some(res) => {
let secret = res.secret().await?;
println!("deleting legacy secret service entry {} {}", service, account);
keyring.delete(&attributes).await?;
let secret_string = String::from_utf8(secret.to_vec())?;
set_password(service, account, &secret_string).await?;
Ok(secret_string)
},
None => Err(anyhow!("no result"))
}
}
attributes
pub async fn set_password(service: &str, account: &str, password: &str) -> Result<()> {
let keyring = oo7::Keyring::new().await?;
let attributes = HashMap::from([("service", service), ("account", account)]);
keyring.create_item("org.freedesktop.Secret.Generic", &attributes, password, true).await?;
Ok(())
}
pub async fn delete_password(service: &str, account: &str) -> Result<()> {
let keyring = oo7::Keyring::new().await?;
let attributes = HashMap::from([("service", service), ("account", account)]);
keyring.delete(&attributes).await?;
Ok(())
}
pub async fn is_available() -> Result<bool> {
match oo7::Keyring::new().await {
Ok(_) => Ok(true),
_ => Ok(false),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({}););
set_password("BitwardenTest", "BitwardenTest", "Random").unwrap();
#[tokio::test]
async fn test() {
set_password("BitwardenTest", "BitwardenTest", "Random").await.unwrap();
assert_eq!(
"Random",
get_password("BitwardenTest", "BitwardenTest").unwrap()
get_password("BitwardenTest", "BitwardenTest").await.unwrap()
);
delete_password("BitwardenTest", "BitwardenTest").unwrap();
delete_password("BitwardenTest", "BitwardenTest").await.unwrap();
// Ensure password is deleted
match get_password("BitwardenTest", "BitwardenTest") {
Ok(_) => panic!("Got a result"),
Err(e) => assert_eq!("No password found", e.to_string()),
match get_password("BitwardenTest", "BitwardenTest").await {
Ok(_) => {
panic!("Got a result")
}
Err(e) => assert_eq!(
"no result",
e.to_string()
),
}
}
#[test]
fn test_error_no_password() {
match get_password("BitwardenTest", "BitwardenTest") {
#[tokio::test]
async fn test_error_no_password() {
match get_password("Unknown", "Unknown").await {
Ok(_) => panic!("Got a result"),
Err(e) => assert_eq!("No password found", e.to_string()),
Err(e) => assert_eq!(
"no result",
e.to_string()
),
}
}
}

View File

@@ -13,7 +13,7 @@ use windows::{
const CRED_FLAGS_NONE: u32 = 0;
pub fn get_password<'a>(service: &str, account: &str) -> Result<String> {
pub async fn get_password<'a>(service: &str, account: &str) -> Result<String> {
let target_name = U16CString::from_str(target_name(service, account))?;
let mut credential: *mut CREDENTIALW = std::ptr::null_mut();
@@ -45,39 +45,7 @@ pub fn get_password<'a>(service: &str, account: &str) -> Result<String> {
Ok(String::from(password))
}
// Remove this after sufficient releases
pub fn get_password_keytar<'a>(service: &str, account: &str) -> Result<String> {
let target_name = U16CString::from_str(target_name(service, account))?;
let mut credential: *mut CREDENTIALW = std::ptr::null_mut();
let credential_ptr = &mut credential;
let result = unsafe {
CredReadW(
PCWSTR(target_name.as_ptr()),
CRED_TYPE_GENERIC,
CRED_FLAGS_NONE,
credential_ptr,
)
};
scopeguard::defer!({
unsafe { CredFree(credential as *mut _) };
});
result?;
let password = unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(
(*credential).CredentialBlob,
(*credential).CredentialBlobSize as usize,
))
};
Ok(String::from(password))
}
pub fn set_password(service: &str, account: &str, password: &str) -> Result<()> {
pub async fn set_password(service: &str, account: &str, password: &str) -> Result<()> {
let mut target_name = U16CString::from_str(target_name(service, account))?;
let mut user_name = U16CString::from_str(account)?;
let last_written = FILETIME {
@@ -108,7 +76,7 @@ pub fn set_password(service: &str, account: &str, password: &str) -> Result<()>
Ok(())
}
pub fn delete_password(service: &str, account: &str) -> Result<()> {
pub async fn delete_password(service: &str, account: &str) -> Result<()> {
let target_name = U16CString::from_str(target_name(service, account))?;
unsafe {
@@ -122,7 +90,7 @@ pub fn delete_password(service: &str, account: &str) -> Result<()> {
Ok(())
}
pub fn is_available() -> Result<bool> {
pub async fn is_available() -> Result<bool> {
Ok(true)
}
@@ -142,36 +110,25 @@ fn convert_error(e: windows::core::Error) -> String {
mod tests {
use super::*;
#[test]
fn test() {
scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({}););
set_password("BitwardenTest", "BitwardenTest", "Random").unwrap();
#[tokio::test]
async fn test() {
set_password("BitwardenTest", "BitwardenTest", "Random").await.unwrap();
assert_eq!(
"Random",
get_password("BitwardenTest", "BitwardenTest").unwrap()
get_password("BitwardenTest", "BitwardenTest").await.unwrap()
);
delete_password("BitwardenTest", "BitwardenTest").unwrap();
delete_password("BitwardenTest", "BitwardenTest").await.unwrap();
// Ensure password is deleted
match get_password("BitwardenTest", "BitwardenTest") {
match get_password("BitwardenTest", "BitwardenTest").await {
Ok(_) => panic!("Got a result"),
Err(e) => assert_eq!("Password not found.", e.to_string()),
}
}
#[test]
fn test_get_password_keytar() {
scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({}););
keytar::set_password("BitwardenTest", "BitwardenTest", "HelloFromKeytar").unwrap();
assert_eq!(
"HelloFromKeytar",
get_password_keytar("BitwardenTest", "BitwardenTest").unwrap()
);
}
#[test]
fn test_error_no_password() {
match get_password("BitwardenTest", "BitwardenTest") {
#[tokio::test]
async fn test_error_no_password() {
match get_password("BitwardenTest", "BitwardenTest").await {
Ok(_) => panic!("Got a result"),
Err(e) => assert_eq!("Password not found.", e.to_string()),
}

View File

@@ -19,7 +19,7 @@ hex = "=0.4.3"
anyhow = "=1.0.93"
desktop_core = { path = "../core" }
napi = { version = "=2.16.13", features = ["async"] }
napi-derive = "=2.16.12"
napi-derive = "=2.16.13"
tokio = { version = "=1.41.1" }
tokio-util = "=0.7.12"
tokio-stream = "=0.1.15"

View File

@@ -6,8 +6,6 @@
export declare namespace passwords {
/** Fetch the stored password from the keychain. */
export function getPassword(service: string, account: string): Promise<string>
/** Fetch the stored password from the keychain that was stored with Keytar. */
export function getPasswordKeytar(service: string, account: string): Promise<string>
/** Save the password to the keychain. Adds an entry if none exists otherwise updates the existing entry. */
export function setPassword(service: string, account: string, password: string): Promise<void>
/** Delete the stored password from the keychain. */
@@ -124,3 +122,6 @@ export declare namespace ipc {
send(message: string): number
}
}
export declare namespace crypto {
export function argon2(secret: Buffer, salt: Buffer, iterations: number, memory: number, parallelism: number): Promise<Buffer>
}

View File

@@ -8,14 +8,7 @@ pub mod passwords {
/// Fetch the stored password from the keychain.
#[napi]
pub async fn get_password(service: String, account: String) -> napi::Result<String> {
desktop_core::password::get_password(&service, &account)
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
/// Fetch the stored password from the keychain that was stored with Keytar.
#[napi]
pub async fn get_password_keytar(service: String, account: String) -> napi::Result<String> {
desktop_core::password::get_password_keytar(&service, &account)
desktop_core::password::get_password(&service, &account).await
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
@@ -26,21 +19,21 @@ pub mod passwords {
account: String,
password: String,
) -> napi::Result<()> {
desktop_core::password::set_password(&service, &account, &password)
desktop_core::password::set_password(&service, &account, &password).await
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
/// Delete the stored password from the keychain.
#[napi]
pub async fn delete_password(service: String, account: String) -> napi::Result<()> {
desktop_core::password::delete_password(&service, &account)
desktop_core::password::delete_password(&service, &account).await
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
// Checks if the os secure storage is available
#[napi]
pub async fn is_available() -> napi::Result<bool> {
desktop_core::password::is_available().map_err(|e| napi::Error::from_reason(e.to_string()))
desktop_core::password::is_available().await.map_err(|e| napi::Error::from_reason(e.to_string()))
}
}
@@ -81,6 +74,7 @@ pub mod biometrics {
key_material.map(|m| m.into()),
&iv_b64,
)
.await
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
@@ -92,6 +86,7 @@ pub mod biometrics {
) -> napi::Result<String> {
let result =
Biometric::get_biometric_secret(&service, &account, key_material.map(|m| m.into()))
.await
.map_err(|e| napi::Error::from_reason(e.to_string()));
result
}
@@ -528,3 +523,22 @@ pub mod ipc {
}
}
}
#[napi]
pub mod crypto {
use napi::bindgen_prelude::Buffer;
#[napi]
pub async fn argon2(
secret: Buffer,
salt: Buffer,
iterations: u32,
memory: u32,
parallelism: u32,
) -> napi::Result<Buffer> {
desktop_core::crypto::argon2(&secret, &salt, iterations, memory, parallelism)
.map_err(|e| napi::Error::from_reason(e.to_string()))
.map(|v| v.to_vec())
.map(|v| Buffer::from(v))
}
}

View File

@@ -18,12 +18,7 @@
"**/*",
"!**/node_modules/@bitwarden/desktop-napi/**/*",
"**/node_modules/@bitwarden/desktop-napi/index.js",
"**/node_modules/@bitwarden/desktop-napi/desktop_napi.${platform}-${arch}*.node",
"!**/node_modules/argon2/**/*",
"**/node_modules/argon2/argon2.cjs",
"**/node_modules/argon2/package.json",
"**/node_modules/argon2/build/Release/argon2.node"
"**/node_modules/@bitwarden/desktop-napi/desktop_napi.${platform}-${arch}*.node"
],
"electronVersion": "32.1.1",
"generateUpdatesFilesForAllChannels": true,
@@ -229,7 +224,7 @@
},
"deb": {
"artifactName": "${productName}-${version}-${arch}.${ext}",
"depends": ["libnotify4", "libxtst6", "libnss3", "libsecret-1-0", "libxss1"]
"depends": ["libnotify4", "libxtst6", "libnss3", "libxss1"]
},
"appImage": {
"artifactName": "${productName}-${version}-${arch}.${ext}"

View File

@@ -19,6 +19,13 @@ export class RendererCryptoFunctionService
memory: number,
parallelism: number,
): Promise<Uint8Array> {
if (typeof password === "string") {
password = new TextEncoder().encode(password);
}
if (typeof salt === "string") {
salt = new TextEncoder().encode(salt);
}
return await ipc.platform.crypto.argon2(password, salt, iterations, memory, parallelism);
}
}

View File

@@ -9,8 +9,7 @@
"version": "2024.12.0",
"license": "GPL-3.0",
"dependencies": {
"@bitwarden/desktop-napi": "file:../desktop_native/napi",
"argon2": "0.41.1"
"@bitwarden/desktop-napi": "file:../desktop_native/napi"
}
},
"../desktop_native/napi": {

View File

@@ -12,7 +12,6 @@
"url": "git+https://github.com/bitwarden/clients.git"
},
"dependencies": {
"@bitwarden/desktop-napi": "file:../desktop_native/napi",
"argon2": "0.41.1"
"@bitwarden/desktop-napi": "file:../desktop_native/napi"
}
}

View File

@@ -1,6 +1,7 @@
import { ipcMain } from "electron";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { crypto } from "@bitwarden/desktop-napi";
import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service";
export class MainCryptoFunctionService
@@ -13,16 +14,16 @@ export class MainCryptoFunctionService
async (
event,
opts: {
password: string | Uint8Array;
salt: string | Uint8Array;
password: Uint8Array;
salt: Uint8Array;
iterations: number;
memory: number;
parallelism: number;
},
) => {
return await this.argon2(
opts.password,
opts.salt,
return await crypto.argon2(
Buffer.from(opts.password),
Buffer.from(opts.salt),
opts.iterations,
opts.memory,
opts.parallelism,

View File

@@ -99,8 +99,8 @@ const nativeMessaging = {
const crypto = {
argon2: (
password: string | Uint8Array,
salt: string | Uint8Array,
password: Uint8Array,
salt: Uint8Array,
iterations: number,
memory: number,
parallelism: number,

View File

@@ -198,7 +198,10 @@ export class SshAgentService implements OnDestroy {
}
const sshCiphers = ciphers.filter(
(cipher) => cipher.type === CipherType.SshKey && !cipher.isDeleted,
(cipher) =>
cipher.type === CipherType.SshKey &&
!cipher.isDeleted &&
cipher.organizationId === null,
);
const keys = sshCiphers.map((cipher) => {
return {

View File

@@ -82,6 +82,7 @@
<li
class="filter-option"
[ngClass]="{ active: activeFilter.cipherType === cipherTypeEnum.SshKey }"
*ngIf="isSshKeysEnabled"
>
<span class="filter-buttons">
<button

View File

@@ -1,9 +1,21 @@
import { Component } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { TypeFilterComponent as BaseTypeFilterComponent } from "@bitwarden/angular/vault/vault-filter/components/type-filter.component";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
@Component({
selector: "app-type-filter",
templateUrl: "type-filter.component.html",
})
export class TypeFilterComponent extends BaseTypeFilterComponent {}
export class TypeFilterComponent extends BaseTypeFilterComponent implements OnInit {
isSshKeysEnabled = false;
constructor(private configService: ConfigService) {
super();
}
async ngOnInit(): Promise<void> {
this.isSshKeysEnabled = await this.configService.getFeatureFlag(FeatureFlag.SSHKeyVaultItem);
}
}

View File

@@ -81,8 +81,6 @@ const main = {
externals: {
"electron-reload": "commonjs2 electron-reload",
"@bitwarden/desktop-napi": "commonjs2 @bitwarden/desktop-napi",
argon2: "commonjs2 argon2",
},
};

View File

@@ -59,8 +59,14 @@ describe("Is Enterprise Org Guard", () => {
{
path: "organizations/:organizationId/enterpriseOrgsOnly",
component: IsEnterpriseOrganizationComponent,
canActivate: [isEnterpriseOrgGuard()],
canActivate: [isEnterpriseOrgGuard(true)],
},
{
path: "organizations/:organizationId/enterpriseOrgsOnlyNoError",
component: IsEnterpriseOrganizationComponent,
canActivate: [isEnterpriseOrgGuard(false)],
},
{
path: "organizations/:organizationId/billing/subscription",
component: OrganizationUpgradeScreenComponent,
@@ -115,6 +121,24 @@ describe("Is Enterprise Org Guard", () => {
);
});
it.each([
ProductTierType.Free,
ProductTierType.Families,
ProductTierType.Teams,
ProductTierType.TeamsStarter,
])("does not proceed with the navigation for productTierType '%s'", async (productTierType) => {
const org = orgFactory({
type: OrganizationUserType.User,
productTierType: productTierType,
});
organizationService.get.calledWith(org.id).mockResolvedValue(org);
await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnlyNoError`);
expect(dialogService.openSimpleDialog).not.toHaveBeenCalled();
expect(
routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "",
).not.toBe("This component can only be accessed by a enterprise organization!");
});
it("proceeds with navigation if the organization in question is a enterprise organization", async () => {
const org = orgFactory({ productTierType: ProductTierType.Enterprise });
organizationService.get.calledWith(org.id).mockResolvedValue(org);

View File

@@ -17,7 +17,7 @@ import { DialogService } from "@bitwarden/components";
* if they have access to upgrade the organization. If the organization is
* enterprise routing proceeds."
*/
export function isEnterpriseOrgGuard(): CanActivateFn {
export function isEnterpriseOrgGuard(showError: boolean = true): CanActivateFn {
return async (route: ActivatedRouteSnapshot, _state: RouterStateSnapshot) => {
const router = inject(Router);
const organizationService = inject(OrganizationService);
@@ -29,7 +29,7 @@ export function isEnterpriseOrgGuard(): CanActivateFn {
return router.createUrlTree(["/"]);
}
if (org.productTierType != ProductTierType.Enterprise) {
if (org.productTierType != ProductTierType.Enterprise && showError) {
// Users without billing permission can't access billing
if (!org.canEditSubscription) {
await dialogService.openSimpleDialog({

View File

@@ -0,0 +1,66 @@
<app-header> </app-header>
<bit-tab-group [(selectedIndex)]="tabIndex">
<bit-tab [label]="'singleSignOn' | i18n">
<section class="tw-mb-9">
<h2 bitTypography="h2">{{ "singleSignOn" | i18n }}</h2>
<p bitTypography="body1">
{{ "ssoDescStart" | i18n }}
<a bitLink routerLink="../settings/sso" class="tw-lowercase">{{ "singleSignOn" | i18n }}</a>
{{ "ssoDescEnd" | i18n }}
</p>
<app-integration-grid
[integrations]="integrationsList | filterIntegrations: IntegrationType.SSO"
></app-integration-grid>
</section>
</bit-tab>
<bit-tab [label]="'userProvisioning' | i18n">
<section class="tw-mb-9">
<h2 bitTypography="h2">
{{ "scimIntegration" | i18n }}
</h2>
<p bitTypography="body1">
{{ "scimIntegrationDescStart" | i18n }}
<a bitLink routerLink="../settings/scim">{{ "scimIntegration" | i18n }}</a>
{{ "scimIntegrationDescEnd" | i18n }}
</p>
<app-integration-grid
[integrations]="integrationsList | filterIntegrations: IntegrationType.SCIM"
></app-integration-grid>
</section>
<section class="tw-mb-9">
<h2 bitTypography="h2">
{{ "bwdc" | i18n }}
</h2>
<p bitTypography="body1">{{ "bwdcDesc" | i18n }}</p>
<app-integration-grid
[integrations]="integrationsList | filterIntegrations: IntegrationType.BWDC"
></app-integration-grid>
</section>
</bit-tab>
<bit-tab [label]="'eventManagement' | i18n">
<section class="tw-mb-9">
<h2 bitTypography="h2">
{{ "eventManagement" | i18n }}
</h2>
<p bitTypography="body1">{{ "eventManagementDesc" | i18n }}</p>
<app-integration-grid
[integrations]="integrationsList | filterIntegrations: IntegrationType.EVENT"
></app-integration-grid>
</section>
</bit-tab>
<bit-tab [label]="'deviceManagement' | i18n">
<section class="tw-mb-9">
<h2 bitTypography="h2">
{{ "deviceManagement" | i18n }}
</h2>
<p bitTypography="body1">{{ "deviceManagementDesc" | i18n }}</p>
<app-integration-grid
[integrations]="integrationsList | filterIntegrations: IntegrationType.DEVICE"
></app-integration-grid>
</section>
</bit-tab>
</bit-tab-group>

View File

@@ -0,0 +1,207 @@
import { Component } from "@angular/core";
import { IntegrationType } from "@bitwarden/common/enums";
import { HeaderModule } from "../../../layouts/header/header.module";
import { FilterIntegrationsPipe, IntegrationGridComponent, Integration } from "../../../shared/";
import { SharedModule } from "../../../shared/shared.module";
import { SharedOrganizationModule } from "../shared";
@Component({
selector: "ac-integrations",
templateUrl: "./integrations.component.html",
standalone: true,
imports: [
SharedModule,
SharedOrganizationModule,
IntegrationGridComponent,
HeaderModule,
FilterIntegrationsPipe,
],
})
export class AdminConsoleIntegrationsComponent {
integrationsList: Integration[] = [];
tabIndex: number;
constructor() {
this.integrationsList = [
{
name: "AD FS",
linkURL: "https://bitwarden.com/help/saml-adfs/",
image: "../../../../../../../images/integrations/azure-active-directory.svg",
type: IntegrationType.SSO,
},
{
name: "Auth0",
linkURL: "https://bitwarden.com/help/saml-auth0/",
image: "../../../../../../../images/integrations/logo-auth0-badge-color.svg",
type: IntegrationType.SSO,
},
{
name: "AWS",
linkURL: "https://bitwarden.com/help/saml-aws/",
image: "../../../../../../../images/integrations/aws-color.svg",
imageDarkMode: "../../../../../../../images/integrations/aws-darkmode.svg",
type: IntegrationType.SSO,
},
{
name: "Microsoft Entra ID",
linkURL: "https://bitwarden.com/help/saml-azure/",
image: "../../../../../../../images/integrations/logo-microsoft-entra-id-color.svg",
type: IntegrationType.SSO,
},
{
name: "Duo",
linkURL: "https://bitwarden.com/help/saml-duo/",
image: "../../../../../../../images/integrations/logo-duo-color.svg",
type: IntegrationType.SSO,
},
{
name: "Google",
linkURL: "https://bitwarden.com/help/saml-google/",
image: "../../../../../../../images/integrations/logo-google-badge-color.svg",
type: IntegrationType.SSO,
},
{
name: "JumpCloud",
linkURL: "https://bitwarden.com/help/saml-jumpcloud/",
image: "../../../../../../../images/integrations/logo-jumpcloud-badge-color.svg",
imageDarkMode: "../../../../../../../images/integrations/jumpcloud-darkmode.svg",
type: IntegrationType.SSO,
},
{
name: "KeyCloak",
linkURL: "https://bitwarden.com/help/saml-keycloak/",
image: "../../../../../../../images/integrations/logo-keycloak-icon.svg",
type: IntegrationType.SSO,
},
{
name: "Okta",
linkURL: "https://bitwarden.com/help/saml-okta/",
image: "../../../../../../../images/integrations/logo-okta-symbol-black.svg",
imageDarkMode: "../../../../../../../images/integrations/okta-darkmode.svg",
type: IntegrationType.SSO,
},
{
name: "OneLogin",
linkURL: "https://bitwarden.com/help/saml-onelogin/",
image: "../../../../../../../images/integrations/logo-onelogin-badge-color.svg",
imageDarkMode: "../../../../../../../images/integrations/onelogin-darkmode.svg",
type: IntegrationType.SSO,
},
{
name: "PingFederate",
linkURL: "https://bitwarden.com/help/saml-pingfederate/",
image: "../../../../../../../images/integrations/logo-ping-identity-badge-color.svg",
type: IntegrationType.SSO,
},
{
name: "Microsoft Entra ID",
linkURL: "https://bitwarden.com/help/microsoft-entra-id-scim-integration/",
image: "../../../../../../../images/integrations/logo-microsoft-entra-id-color.svg",
type: IntegrationType.SCIM,
},
{
name: "Okta",
linkURL: "https://bitwarden.com/help/okta-scim-integration/",
image: "../../../../../../../images/integrations/logo-okta-symbol-black.svg",
imageDarkMode: "../../../../../../../images/integrations/okta-darkmode.svg",
type: IntegrationType.SCIM,
},
{
name: "OneLogin",
linkURL: "https://bitwarden.com/help/onelogin-scim-integration/",
image: "../../../../../../../images/integrations/logo-onelogin-badge-color.svg",
imageDarkMode: "../../../../../../../images/integrations/onelogin-darkmode.svg",
type: IntegrationType.SCIM,
},
{
name: "JumpCloud",
linkURL: "https://bitwarden.com/help/jumpcloud-scim-integration/",
image: "../../../../../../../images/integrations/logo-jumpcloud-badge-color.svg",
imageDarkMode: "../../../../../../../images/integrations/jumpcloud-darkmode.svg",
type: IntegrationType.SCIM,
},
{
name: "Ping Identity",
linkURL: "https://bitwarden.com/help/ping-identity-scim-integration/",
image: "../../../../../../../images/integrations/logo-ping-identity-badge-color.svg",
type: IntegrationType.SCIM,
},
{
name: "Active Directory",
linkURL: "https://bitwarden.com/help/ldap-directory/",
image: "../../../../../../../images/integrations/azure-active-directory.svg",
type: IntegrationType.BWDC,
},
{
name: "Microsoft Entra ID",
linkURL: "https://bitwarden.com/help/microsoft-entra-id/",
image: "../../../../../../../images/integrations/logo-microsoft-entra-id-color.svg",
type: IntegrationType.BWDC,
},
{
name: "Google Workspace",
linkURL: "https://bitwarden.com/help/workspace-directory/",
image: "../../../../../../../images/integrations/logo-google-badge-color.svg",
type: IntegrationType.BWDC,
},
{
name: "Okta",
linkURL: "https://bitwarden.com/help/okta-directory/",
image: "../../../../../../../images/integrations/logo-okta-symbol-black.svg",
imageDarkMode: "../../../../../../../images/integrations/okta-darkmode.svg",
type: IntegrationType.BWDC,
},
{
name: "OneLogin",
linkURL: "https://bitwarden.com/help/onelogin-directory/",
image: "../../../../../../../images/integrations/logo-onelogin-badge-color.svg",
imageDarkMode: "../../../../../../../images/integrations/onelogin-darkmode.svg",
type: IntegrationType.BWDC,
},
{
name: "Splunk",
linkURL: "https://bitwarden.com/help/splunk-siem/",
image: "../../../../../../../images/integrations/logo-splunk-black.svg",
imageDarkMode: "../../../../../../../images/integrations/splunk-darkmode.svg",
type: IntegrationType.EVENT,
},
{
name: "Microsoft Sentinel",
linkURL: "https://bitwarden.com/help/microsoft-sentinel-siem/",
image: "../../../../../../../images/integrations/logo-microsoft-sentinel-color.svg",
type: IntegrationType.EVENT,
},
{
name: "Rapid7",
linkURL: "https://bitwarden.com/help/rapid7-siem/",
image: "../../../../../../../images/integrations/logo-rapid7-black.svg",
imageDarkMode: "../../../../../../../images/integrations/rapid7-darkmode.svg",
type: IntegrationType.EVENT,
},
{
name: "Elastic",
linkURL: "https://bitwarden.com/help/elastic-siem/",
image: "../../../../../../../images/integrations/logo-elastic-badge-color.svg",
type: IntegrationType.EVENT,
},
{
name: "Panther",
linkURL: "https://bitwarden.com/help/panther-siem/",
image: "../../../../../../../images/integrations/logo-panther-round-color.svg",
type: IntegrationType.EVENT,
},
{
name: "Microsoft Intune",
linkURL: "https://bitwarden.com/help/deploy-browser-extensions-with-intune/",
image: "../../../../../../../images/integrations/logo-microsoft-intune-color.svg",
type: IntegrationType.DEVICE,
},
];
}
get IntegrationType(): typeof IntegrationType {
return IntegrationType;
}
}

View File

@@ -4,7 +4,7 @@
<org-switcher [filter]="orgFilter" [hideNewButton]="hideNewOrgButton$ | async"></org-switcher>
<bit-nav-group
icon="bwi-filter"
*ngIf="isAccessIntelligenceFeatureEnabled"
*ngIf="organization.useRiskInsights"
[text]="'accessIntelligence' | i18n"
>
<bit-nav-item
@@ -60,6 +60,12 @@
<bit-nav-item [text]="'billingHistory' | i18n" route="billing/history"></bit-nav-item>
</ng-container>
</bit-nav-group>
<bit-nav-item
icon="bwi-providers"
[text]="'integrations' | i18n"
route="integrations"
*ngIf="integrationPageEnabled$ | async"
></bit-nav-item>
<bit-nav-group
icon="bwi-cog"
[text]="'settings' | i18n"

View File

@@ -18,6 +18,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { PolicyType, ProviderStatusType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -47,12 +48,15 @@ export class OrganizationLayoutComponent implements OnInit {
protected orgFilter = (org: Organization) => canAccessOrgAdmin(org);
protected integrationPageEnabled$: Observable<boolean>;
organization$: Observable<Organization>;
canAccessExport$: Observable<boolean>;
showPaymentAndHistory$: Observable<boolean>;
hideNewOrgButton$: Observable<boolean>;
organizationIsUnmanaged$: Observable<boolean>;
isAccessIntelligenceFeatureEnabled = false;
enterpriseOrganization$: Observable<boolean>;
constructor(
private route: ActivatedRoute,
@@ -66,10 +70,6 @@ export class OrganizationLayoutComponent implements OnInit {
async ngOnInit() {
document.body.classList.remove("layout_frontend");
this.isAccessIntelligenceFeatureEnabled = await this.configService.getFeatureFlag(
FeatureFlag.AccessIntelligence,
);
this.organization$ = this.route.params.pipe(
map((p) => p.organizationId),
switchMap((id) => this.organizationService.organizations$.pipe(getById(id))),
@@ -104,6 +104,16 @@ export class OrganizationLayoutComponent implements OnInit {
provider.providerStatus !== ProviderStatusType.Billable,
),
);
this.integrationPageEnabled$ = combineLatest(
this.organization$,
this.configService.getFeatureFlag$(FeatureFlag.PM14505AdminConsoleIntegrationPage),
).pipe(
map(
([org, featureFlagEnabled]) =>
org.productTierType === ProductTierType.Enterprise && featureFlagEnabled,
),
);
}
canShowVaultTab(organization: Organization): boolean {

View File

@@ -487,6 +487,11 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
this.organization.productTierType === ProductTierType.TeamsStarter ||
this.organization.productTierType === ProductTierType.Families)
) {
if (!this.organization.canEditSubscription) {
await this.showSeatLimitReachedDialog();
return;
}
const reference = openChangePlanDialog(this.dialogService, {
data: {
organizationId: this.organization.id,

View File

@@ -2,6 +2,7 @@ import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { authGuard } from "@bitwarden/angular/auth/guards";
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
import {
canAccessOrgAdmin,
canAccessGroupsTab,
@@ -11,6 +12,7 @@ import {
canAccessSettingsTab,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { organizationPermissionsGuard } from "../../admin-console/organizations/guards/org-permissions.guard";
import { organizationRedirectGuard } from "../../admin-console/organizations/guards/org-redirect.guard";
@@ -18,6 +20,8 @@ import { OrganizationLayoutComponent } from "../../admin-console/organizations/l
import { deepLinkGuard } from "../../auth/guards/deep-link.guard";
import { VaultModule } from "../../vault/org-vault/vault.module";
import { isEnterpriseOrgGuard } from "./guards/is-enterprise-org.guard";
import { AdminConsoleIntegrationsComponent } from "./integrations/integrations.component";
import { GroupsComponent } from "./manage/groups.component";
const routes: Routes = [
@@ -36,6 +40,17 @@ const routes: Routes = [
path: "vault",
loadChildren: () => VaultModule,
},
{
path: "integrations",
canActivate: [
canAccessFeature(FeatureFlag.PM14505AdminConsoleIntegrationPage),
isEnterpriseOrgGuard(false),
],
component: AdminConsoleIntegrationsComponent,
data: {
titleId: "integrations",
},
},
{
path: "settings",
loadChildren: () =>

View File

@@ -45,7 +45,7 @@ export abstract class BasePolicyComponent implements OnInit {
return null;
}
buildRequest(policiesEnabledMap: Map<PolicyType, boolean>) {
buildRequest() {
const request = new PolicyRequest();
request.enabled = this.enabled.value;
request.type = this.policy.type;

View File

@@ -87,7 +87,6 @@ export class PoliciesComponent implements OnInit {
data: {
policy: policy,
organizationId: this.organizationId,
policiesEnabledMap: this.policiesEnabledMap,
},
});

View File

@@ -15,7 +15,6 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { PolicyRequest } from "@bitwarden/common/admin-console/models/request/policy.request";
import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DialogService, ToastService } from "@bitwarden/components";
import { BasePolicy, BasePolicyComponent } from "../policies";
@@ -25,8 +24,6 @@ export type PolicyEditDialogData = {
policy: BasePolicy;
/** Returns a unique organization id */
organizationId: string;
/** A map indicating whether each policy type is enabled or disabled. */
policiesEnabledMap: Map<PolicyType, boolean>;
};
export enum PolicyEditDialogResult {
@@ -55,7 +52,6 @@ export class PolicyEditComponent implements AfterViewInit {
@Inject(DIALOG_DATA) protected data: PolicyEditDialogData,
private policyApiService: PolicyApiServiceAbstraction,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private cdr: ChangeDetectorRef,
private formBuilder: FormBuilder,
private dialogRef: DialogRef<PolicyEditDialogResult>,
@@ -99,7 +95,7 @@ export class PolicyEditComponent implements AfterViewInit {
submit = async () => {
let request: PolicyRequest;
try {
request = await this.policyComponent.buildRequest(this.data.policiesEnabledMap);
request = await this.policyComponent.buildRequest();
} catch (e) {
this.toastService.showToast({ variant: "error", title: null, message: e.message });
return;

View File

@@ -2,10 +2,6 @@ import { Component } from "@angular/core";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { PolicyRequest } from "@bitwarden/common/admin-console/models/request/policy.request";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
@@ -24,25 +20,4 @@ export class RequireSsoPolicy extends BasePolicy {
selector: "policy-require-sso",
templateUrl: "require-sso.component.html",
})
export class RequireSsoPolicyComponent extends BasePolicyComponent {
constructor(
private i18nService: I18nService,
private configService: ConfigService,
) {
super();
}
async buildRequest(policiesEnabledMap: Map<PolicyType, boolean>): Promise<PolicyRequest> {
if (await this.configService.getFeatureFlag(FeatureFlag.Pm13322AddPolicyDefinitions)) {
// We are now relying on server-side validation only
return super.buildRequest(policiesEnabledMap);
}
const singleOrgEnabled = policiesEnabledMap.get(PolicyType.SingleOrg) ?? false;
if (this.enabled.value && !singleOrgEnabled) {
throw new Error(this.i18nService.t("requireSsoPolicyReqError"));
}
return super.buildRequest(policiesEnabledMap);
}
}
export class RequireSsoPolicyComponent extends BasePolicyComponent {}

View File

@@ -2,10 +2,8 @@ import { Component, OnInit } from "@angular/core";
import { firstValueFrom, Observable } from "rxjs";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { PolicyRequest } from "@bitwarden/common/admin-console/models/request/policy.request";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
@@ -21,10 +19,7 @@ export class SingleOrgPolicy extends BasePolicy {
templateUrl: "single-org.component.html",
})
export class SingleOrgPolicyComponent extends BasePolicyComponent implements OnInit {
constructor(
private i18nService: I18nService,
private configService: ConfigService,
) {
constructor(private configService: ConfigService) {
super();
}
@@ -44,39 +39,4 @@ export class SingleOrgPolicyComponent extends BasePolicyComponent implements OnI
this.enabled.disable();
}
}
async buildRequest(policiesEnabledMap: Map<PolicyType, boolean>): Promise<PolicyRequest> {
if (await this.configService.getFeatureFlag(FeatureFlag.Pm13322AddPolicyDefinitions)) {
// We are now relying on server-side validation only
return super.buildRequest(policiesEnabledMap);
}
if (!this.enabled.value) {
if (policiesEnabledMap.get(PolicyType.RequireSso) ?? false) {
throw new Error(
this.i18nService.t("disableRequiredError", this.i18nService.t("requireSso")),
);
}
if (policiesEnabledMap.get(PolicyType.MaximumVaultTimeout) ?? false) {
throw new Error(
this.i18nService.t(
"disableRequiredError",
this.i18nService.t("maximumVaultTimeoutLabel"),
),
);
}
if (
(await firstValueFrom(this.accountDeprovisioningEnabled$)) &&
!this.policyResponse.canToggleState
) {
throw new Error(
this.i18nService.t("disableRequiredError", this.i18nService.t("singleOrg")),
);
}
}
return super.buildRequest(policiesEnabledMap);
}
}

View File

@@ -0,0 +1,4 @@
export * from "./integrations/integration-card/integration-card.component";
export * from "./integrations/integration-grid/integration-grid.component";
export * from "./integrations/integrations.pipe";
export * from "./integrations/models";

View File

@@ -1,8 +1,11 @@
<div
class="tw-block tw-h-full tw-overflow-hidden tw-rounded tw-border tw-border-solid tw-border-secondary-600 tw-relative tw-transition-all xl:tw-w-64 hover:tw-scale-105 focus-within:tw-outline-none focus-within:tw-ring focus-within:tw-ring-primary-700 focus-within:tw-ring-offset-2"
class="tw-block tw-h-full tw-overflow-hidden tw-rounded tw-border tw-border-solid tw-border-secondary-600 tw-relative tw-transition-all hover:tw-scale-105 focus-within:tw-outline-none focus-within:tw-ring focus-within:tw-ring-primary-700 focus-within:tw-ring-offset-2"
>
<div class="tw-flex tw-bg-secondary-100 tw-items-center tw-justify-end tw-pt-4 tw-pr-4">
<i class="bwi bwi-external-link"></i>
</div>
<div
class="tw-flex tw-h-36 tw-bg-secondary-100 tw-items-center tw-justify-center tw-py-2 tw-px-6 lg:tw-py-4 lg:tw-px-12"
class="tw-flex tw-h-32 tw-bg-secondary-100 tw-items-center tw-justify-center tw-pb-2 tw-px-6 lg:tw-pb-4 lg:tw-px-12"
>
<div class="tw-flex tw-items-center tw-justify-center tw-h-28 tw-w-28 lg:tw-w-40">
<img
@@ -14,14 +17,13 @@
</div>
</div>
<div class="tw-p-5">
<h3 class="tw-mb-4 tw-text-lg tw-font-semibold">{{ name }}</h3>
<h3 class="tw-text-main tw-text-lg tw-font-semibold">{{ name }}</h3>
<a
class="tw-block tw-mb-0 tw-font-bold hover:tw-no-underline focus:tw-outline-none after:tw-content-[''] after:tw-block after:tw-absolute after:tw-w-full after:tw-h-full after:tw-left-0 after:tw-top-0"
[href]="linkURL"
rel="noopener noreferrer"
target="_blank"
>
{{ linkText }}
</a>
<span *ngIf="showNewBadge()" bitBadge class="tw-mt-3" variant="secondary">
{{ "new" | i18n }}

View File

@@ -1,9 +1,13 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { mock } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs";
import { SYSTEM_THEME_OBSERVABLE } from "../../../../../../../libs/angular/src/services/injection-tokens";
import { ThemeType } from "../../../../../../../libs/common/src/platform/enums";
import { ThemeStateService } from "../../../../../../../libs/common/src/platform/theming/theme-state.service";
import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { ThemeType } from "@bitwarden/common/platform/enums";
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { SharedModule } from "@bitwarden/components/src/shared";
import { I18nPipe } from "@bitwarden/components/src/shared/i18n.pipe";
import { IntegrationCardComponent } from "./integration-card.component";
@@ -19,7 +23,7 @@ describe("IntegrationCardComponent", () => {
systemTheme$.next(ThemeType.Light);
await TestBed.configureTestingModule({
declarations: [IntegrationCardComponent],
imports: [IntegrationCardComponent, SharedModule],
providers: [
{
provide: ThemeStateService,
@@ -29,6 +33,14 @@ describe("IntegrationCardComponent", () => {
provide: SYSTEM_THEME_OBSERVABLE,
useValue: systemTheme$,
},
{
provide: I18nPipe,
useValue: mock<I18nPipe>(),
},
{
provide: I18nService,
useValue: mock<I18nService>(),
},
],
}).compileComponents();
});
@@ -39,7 +51,6 @@ describe("IntegrationCardComponent", () => {
component.name = "Integration Name";
component.image = "test-image.png";
component.linkText = "Get started with integration";
component.linkURL = "https://example.com/";
fixture.detectChanges();
@@ -53,10 +64,8 @@ describe("IntegrationCardComponent", () => {
it("renders card body", () => {
const name = fixture.nativeElement.querySelector("h3");
const link = fixture.nativeElement.querySelector("a");
expect(name.textContent).toBe("Integration Name");
expect(link.textContent.trim()).toBe("Get started with integration");
});
it("assigns external rel attribute", () => {

View File

@@ -13,9 +13,13 @@ import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-t
import { ThemeType } from "@bitwarden/common/platform/enums";
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { SharedModule } from "../../../shared.module";
@Component({
selector: "sm-integration-card",
selector: "app-integration-card",
templateUrl: "./integration-card.component.html",
standalone: true,
imports: [SharedModule],
})
export class IntegrationCardComponent implements AfterViewInit, OnDestroy {
private destroyed$: Subject<void> = new Subject();
@@ -24,7 +28,6 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy {
@Input() name: string;
@Input() image: string;
@Input() imageDarkMode?: string;
@Input() linkText: string;
@Input() linkURL: string;
/** Adds relevant `rel` attribute to external links */

View File

@@ -0,0 +1,63 @@
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { of } from "rxjs";
import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { ThemeTypes } from "@bitwarden/common/platform/enums";
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { I18nMockService } from "@bitwarden/components";
import { SharedModule } from "../../../shared.module";
import { IntegrationCardComponent } from "./integration-card.component";
class MockThemeService implements Partial<ThemeStateService> {}
export default {
title: "Web/Integration Layout/Integration Card",
component: IntegrationCardComponent,
decorators: [
moduleMetadata({
imports: [SharedModule],
providers: [
{
provide: I18nService,
useFactory: () => {
return new I18nMockService({});
},
},
{
provide: ThemeStateService,
useClass: MockThemeService,
},
{
provide: SYSTEM_THEME_OBSERVABLE,
useValue: of(ThemeTypes.Light),
},
],
}),
],
args: {
integrations: [],
},
} as Meta;
type Story = StoryObj<IntegrationCardComponent>;
export const Default: Story = {
render: (args) => ({
props: args,
template: /*html*/ `
<app-integration-card
[name]="name"
[image]="image"
[linkURL]="linkURL"
></app-integration-card>
`,
}),
args: {
name: "Bitwarden",
image: "/integrations/bitwarden-vertical-blue.svg",
linkURL: "https://bitwarden.com",
},
};

View File

@@ -1,15 +1,18 @@
<ul
class="tw-inline-grid tw-grid-cols-3 tw-gap-6 tw-m-0 tw-p-0 tw-w-full tw-auto-cols-auto tw-list-none lg:tw-grid-cols-4 lg:tw-gap-10 lg:tw-w-auto"
>
<li *ngFor="let integration of integrations">
<sm-integration-card
<li
*ngFor="let integration of integrations"
[title]="tooltipI18nKey | i18n: integration.name"
[attr.aria-label]="ariaI18nKey | i18n: integration.name"
>
<app-integration-card
[name]="integration.name"
[linkText]="integration.linkText"
[linkURL]="integration.linkURL"
[image]="integration.image"
[imageDarkMode]="integration.imageDarkMode"
[externalURL]="integration.type === IntegrationType.SDK"
[newBadgeExpiration]="integration.newBadgeExpiration"
></sm-integration-card>
></app-integration-card>
</li>
</ul>

View File

@@ -3,12 +3,16 @@ import { By } from "@angular/platform-browser";
import { mock } from "jest-mock-extended";
import { of } from "rxjs";
import { SYSTEM_THEME_OBSERVABLE } from "../../../../../../../libs/angular/src/services/injection-tokens";
import { IntegrationType } from "../../../../../../../libs/common/src/enums";
import { ThemeType } from "../../../../../../../libs/common/src/platform/enums";
import { ThemeStateService } from "../../../../../../../libs/common/src/platform/theming/theme-state.service";
import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens";
import { IntegrationType } from "@bitwarden/common/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { ThemeTypes } from "@bitwarden/common/platform/enums";
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { SharedModule } from "@bitwarden/components/src/shared";
import { I18nPipe } from "@bitwarden/components/src/shared/i18n.pipe";
import { IntegrationCardComponent } from "../integration-card/integration-card.component";
import { Integration } from "../models/integration";
import { Integration } from "../models";
import { IntegrationGridComponent } from "./integration-grid.component";
@@ -19,14 +23,12 @@ describe("IntegrationGridComponent", () => {
{
name: "Integration 1",
image: "test-image1.png",
linkText: "Get started with integration 1",
linkURL: "https://example.com/1",
type: IntegrationType.Integration,
},
{
name: "SDK 2",
image: "test-image2.png",
linkText: "View SDK 2",
linkURL: "https://example.com/2",
type: IntegrationType.SDK,
},
@@ -34,7 +36,7 @@ describe("IntegrationGridComponent", () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [IntegrationGridComponent, IntegrationCardComponent],
imports: [IntegrationGridComponent, IntegrationCardComponent, SharedModule],
providers: [
{
provide: ThemeStateService,
@@ -42,7 +44,15 @@ describe("IntegrationGridComponent", () => {
},
{
provide: SYSTEM_THEME_OBSERVABLE,
useValue: of(ThemeType.Light),
useValue: of(ThemeTypes.Light),
},
{
provide: I18nPipe,
useValue: mock<I18nPipe>(),
},
{
provide: I18nService,
useValue: mock<I18nService>({ t: (key, p1) => key + " " + p1 }),
},
],
});
@@ -50,6 +60,8 @@ describe("IntegrationGridComponent", () => {
fixture = TestBed.createComponent(IntegrationGridComponent);
component = fixture.componentInstance;
component.integrations = integrations;
component.ariaI18nKey = "integrationCardAriaLabel";
component.tooltipI18nKey = "integrationCardTooltip";
fixture.detectChanges();
});
@@ -68,7 +80,6 @@ describe("IntegrationGridComponent", () => {
expect(card.componentInstance.name).toBe("SDK 2");
expect(card.componentInstance.image).toBe("test-image2.png");
expect(card.componentInstance.linkText).toBe("View SDK 2");
expect(card.componentInstance.linkURL).toBe("https://example.com/2");
});
@@ -78,4 +89,12 @@ describe("IntegrationGridComponent", () => {
expect(card[0].componentInstance.externalURL).toBe(false);
expect(card[1].componentInstance.externalURL).toBe(true);
});
it("has a tool tip and aria label attributes", () => {
const card: HTMLElement = fixture.debugElement.queryAll(By.css("li"))[0].nativeElement;
expect(card.title).toBe("integrationCardTooltip" + " " + integrations[0].name);
expect(card.getAttribute("aria-label")).toBe(
"integrationCardAriaLabel" + " " + integrations[0].name,
);
});
});

View File

@@ -0,0 +1,22 @@
import { Component, Input } from "@angular/core";
import { IntegrationType } from "@bitwarden/common/enums";
import { SharedModule } from "../../../shared.module";
import { IntegrationCardComponent } from "../integration-card/integration-card.component";
import { Integration } from "../models";
@Component({
selector: "app-integration-grid",
templateUrl: "./integration-grid.component.html",
standalone: true,
imports: [IntegrationCardComponent, SharedModule],
})
export class IntegrationGridComponent {
@Input() integrations: Integration[];
@Input() ariaI18nKey: string = "integrationCardAriaLabel";
@Input() tooltipI18nKey: string = "integrationCardTooltip";
protected IntegrationType = IntegrationType;
}

View File

@@ -0,0 +1,77 @@
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { of } from "rxjs";
import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens";
import { IntegrationType } from "@bitwarden/common/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { ThemeTypes } from "@bitwarden/common/platform/enums";
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { I18nMockService } from "@bitwarden/components";
import { SharedModule } from "../../../shared.module";
import { IntegrationCardComponent } from "../integration-card/integration-card.component";
import { IntegrationGridComponent } from "../integration-grid/integration-grid.component";
class MockThemeService implements Partial<ThemeStateService> {}
export default {
title: "Web/Integration Layout/Integration Grid",
component: IntegrationGridComponent,
decorators: [
moduleMetadata({
imports: [IntegrationCardComponent, SharedModule],
providers: [
{
provide: I18nService,
useFactory: () => {
return new I18nMockService({
integrationCardAriaLabel: "Go to integration",
integrationCardTooltip: "Go to integration",
});
},
},
{
provide: ThemeStateService,
useClass: MockThemeService,
},
{
provide: SYSTEM_THEME_OBSERVABLE,
useValue: of(ThemeTypes.Dark),
},
],
}),
],
} as Meta;
type Story = StoryObj<IntegrationGridComponent>;
export const Default: Story = {
render: (args) => ({
props: args,
template: /*html*/ `
<app-integration-grid [integrations]="integrations"></app-integration-grid>
`,
}),
args: {
integrations: [
{
name: "Card 1",
linkURL: "https://bitwarden.com",
image: "/integrations/bitwarden-vertical-blue.svg",
type: IntegrationType.SSO,
},
{
name: "Card 2",
linkURL: "https://bitwarden.com",
image: "/integrations/bitwarden-vertical-blue.svg",
type: IntegrationType.SDK,
},
{
name: "Card 3",
linkURL: "https://bitwarden.com",
image: "/integrations/bitwarden-vertical-blue.svg",
type: IntegrationType.SCIM,
},
],
},
};

View File

@@ -0,0 +1,15 @@
import { Pipe, PipeTransform } from "@angular/core";
import { IntegrationType } from "@bitwarden/common/enums";
import { Integration } from "../../../shared/components/integrations/models";
@Pipe({
name: "filterIntegrations",
standalone: true,
})
export class FilterIntegrationsPipe implements PipeTransform {
transform(integrations: Integration[], type: IntegrationType): Integration[] {
return integrations.filter((integration) => integration.type === type);
}
}

View File

@@ -9,7 +9,6 @@ export type Integration = {
*/
imageDarkMode?: string;
linkURL: string;
linkText: string;
type: IntegrationType;
/**
* Shows the "New" badge until the defined date.

View File

@@ -1,2 +1,3 @@
export * from "./shared.module";
export * from "./loose-components.module";
export * from "./components/index";

View File

@@ -281,17 +281,19 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
// If the cipher was newly created (via add/clone), switch the form to edit for subsequent edits.
if (this._originalFormMode === "add" || this._originalFormMode === "clone") {
this.formConfig.mode = "edit";
this.formConfig.initialValues = null;
}
let cipher: Cipher;
let cipher = await this.cipherService.get(cipherView.id);
// When the form config is used within the Admin Console, retrieve the cipher from the admin endpoint
if (this.formConfig.isAdminConsole) {
// When the form config is used within the Admin Console, retrieve the cipher from the admin endpoint (if not found in local state)
if (this.formConfig.isAdminConsole && (cipher == null || this.formConfig.admin)) {
const cipherResponse = await this.apiService.getCipherAdmin(cipherView.id);
cipherResponse.edit = true;
cipherResponse.viewPassword = true;
const cipherData = new CipherData(cipherResponse);
cipher = new Cipher(cipherData);
} else {
cipher = await this.cipherService.get(cipherView.id);
}
// Store the updated cipher so any following edits use the most up to date cipher

View File

@@ -6,6 +6,8 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { CipherType } from "@bitwarden/common/vault/enums";
@@ -94,6 +96,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
protected platformUtilsService: PlatformUtilsService,
protected billingApiService: BillingApiServiceAbstraction,
protected dialogService: DialogService,
protected configService: ConfigService,
) {}
async ngOnInit(): Promise<void> {
@@ -258,13 +261,16 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
type: CipherType.SecureNote,
icon: "bwi-sticky-note",
},
{
];
if (await this.configService.getFeatureFlag(FeatureFlag.SSHKeyVaultItem)) {
allTypeFilters.push({
id: "sshKey",
name: this.i18nService.t("typeSshKey"),
type: CipherType.SshKey,
icon: "bwi-key",
},
];
});
}
const typeFilterSection: VaultFilterSection = {
data$: this.vaultFilterService.buildTypeTree(

View File

@@ -8,6 +8,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { CipherId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { RoutedVaultFilterService } from "../../individual-vault/vault-filter/services/routed-vault-filter.service";
@@ -52,12 +53,16 @@ describe("AdminConsoleCipherFormConfigService", () => {
const organization$ = new BehaviorSubject<Organization>(testOrg as Organization);
const organizations$ = new BehaviorSubject<Organization[]>([testOrg, testOrg2] as Organization[]);
const getCipherAdmin = jest.fn().mockResolvedValue(null);
const getCipher = jest.fn().mockResolvedValue(null);
beforeEach(async () => {
getCipherAdmin.mockClear();
getCipherAdmin.mockResolvedValue({ id: cipherId, name: "Test Cipher - (admin)" });
await TestBed.configureTestingModule({
getCipher.mockClear();
getCipher.mockResolvedValue({ id: cipherId, name: "Test Cipher" });
TestBed.configureTestingModule({
providers: [
AdminConsoleCipherFormConfigService,
{ provide: OrganizationService, useValue: { get$: () => organization$, organizations$ } },
@@ -74,14 +79,14 @@ describe("AdminConsoleCipherFormConfigService", () => {
useValue: { filter$: new BehaviorSubject({ organizationId: testOrg.id }) },
},
{ provide: ApiService, useValue: { getCipherAdmin } },
{ provide: CipherService, useValue: { get: getCipher } },
],
});
adminConsoleConfigService = TestBed.inject(AdminConsoleCipherFormConfigService);
});
describe("buildConfig", () => {
it("sets individual attributes", async () => {
adminConsoleConfigService = TestBed.inject(AdminConsoleCipherFormConfigService);
const { folders, hideIndividualVaultFields } = await adminConsoleConfigService.buildConfig(
"add",
cipherId,
@@ -92,8 +97,6 @@ describe("AdminConsoleCipherFormConfigService", () => {
});
it("sets mode based on passed mode", async () => {
adminConsoleConfigService = TestBed.inject(AdminConsoleCipherFormConfigService);
const { mode } = await adminConsoleConfigService.buildConfig("edit", cipherId);
expect(mode).toBe("edit");
@@ -122,8 +125,6 @@ describe("AdminConsoleCipherFormConfigService", () => {
});
it("sets `allowPersonalOwnership`", async () => {
adminConsoleConfigService = TestBed.inject(AdminConsoleCipherFormConfigService);
policyAppliesToActiveUser$.next(true);
let result = await adminConsoleConfigService.buildConfig("clone", cipherId);
@@ -138,8 +139,6 @@ describe("AdminConsoleCipherFormConfigService", () => {
});
it("disables personal ownership when not cloning", async () => {
adminConsoleConfigService = TestBed.inject(AdminConsoleCipherFormConfigService);
policyAppliesToActiveUser$.next(false);
let result = await adminConsoleConfigService.buildConfig("add", cipherId);
@@ -172,14 +171,32 @@ describe("AdminConsoleCipherFormConfigService", () => {
expect(result.organizations).toEqual([testOrg, testOrg2]);
});
it("retrieves the cipher from the admin service", async () => {
it("retrieves the cipher from the admin service when canEditAllCiphers is true", async () => {
getCipherAdmin.mockResolvedValue({ id: cipherId, name: "Test Cipher - (admin)" });
testOrg.canEditAllCiphers = true;
adminConsoleConfigService = TestBed.inject(AdminConsoleCipherFormConfigService);
await adminConsoleConfigService.buildConfig("add", cipherId);
await adminConsoleConfigService.buildConfig("edit", cipherId);
expect(getCipherAdmin).toHaveBeenCalledWith(cipherId);
});
it("retrieves the cipher from the admin service when not found in local state", async () => {
getCipherAdmin.mockResolvedValue({ id: cipherId, name: "Test Cipher - (admin)" });
testOrg.canEditAllCiphers = false;
getCipher.mockResolvedValue(null);
await adminConsoleConfigService.buildConfig("edit", cipherId);
expect(getCipherAdmin).toHaveBeenCalledWith(cipherId);
});
it("retrieves the cipher from local state when admin is not required", async () => {
testOrg.canEditAllCiphers = false;
await adminConsoleConfigService.buildConfig("edit", cipherId);
expect(getCipherAdmin).not.toHaveBeenCalled();
expect(getCipher).toHaveBeenCalledWith(cipherId);
});
});
});

View File

@@ -5,8 +5,10 @@ import { CollectionAdminService } from "@bitwarden/admin-console/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType, OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums";
import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { CipherId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
@@ -25,6 +27,7 @@ export class AdminConsoleCipherFormConfigService implements CipherFormConfigServ
private organizationService: OrganizationService = inject(OrganizationService);
private routedVaultFilterService: RoutedVaultFilterService = inject(RoutedVaultFilterService);
private collectionAdminService: CollectionAdminService = inject(CollectionAdminService);
private cipherService: CipherService = inject(CipherService);
private apiService: ApiService = inject(ApiService);
private allowPersonalOwnership$ = this.policyService
@@ -57,7 +60,6 @@ export class AdminConsoleCipherFormConfigService implements CipherFormConfigServ
cipherId?: CipherId,
cipherType?: CipherType,
): Promise<CipherFormConfig> {
const cipher = await this.getCipher(cipherId);
const [organization, allowPersonalOwnership, allOrganizations, allCollections] =
await firstValueFrom(
combineLatest([
@@ -74,7 +76,7 @@ export class AdminConsoleCipherFormConfigService implements CipherFormConfigServ
// Only allow the user to assign to their personal vault when cloning and
// the policies are enabled for it.
const allowPersonalOwnershipOnlyForClone = mode === "clone" ? allowPersonalOwnership : false;
const cipher = await this.getCipher(cipherId, organization);
return {
mode,
cipherType: cipher?.type ?? cipherType ?? CipherType.Login,
@@ -89,14 +91,26 @@ export class AdminConsoleCipherFormConfigService implements CipherFormConfigServ
};
}
private async getCipher(id?: CipherId): Promise<Cipher | null> {
private async getCipher(id: CipherId | null, organization: Organization): Promise<Cipher | null> {
if (id == null) {
return Promise.resolve(null);
return null;
}
// Retrieve the cipher through the means of an admin
const localCipher = await this.cipherService.get(id);
// Fetch from the API because we don't need the permissions in local state OR the cipher was not found (e.g. unassigned)
if (organization.canEditAllCiphers || localCipher == null) {
return await this.getCipherFromAdminApi(id);
}
return localCipher;
}
private async getCipherFromAdminApi(id: CipherId): Promise<Cipher> {
const cipherResponse = await this.apiService.getCipherAdmin(id);
// Ensure admin response includes permissions that allow editing
cipherResponse.edit = true;
cipherResponse.viewPassword = true;
const cipherData = new CipherData(cipherResponse);
return new Cipher(cipherData);

View File

@@ -4,6 +4,7 @@ import { firstValueFrom, Subject } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
@@ -42,6 +43,7 @@ export class VaultFilterComponent
protected platformUtilsService: PlatformUtilsService,
protected billingApiService: BillingApiServiceAbstraction,
protected dialogService: DialogService,
protected configService: ConfigService,
) {
super(
vaultFilterService,
@@ -50,6 +52,7 @@ export class VaultFilterComponent
platformUtilsService,
billingApiService,
dialogService,
configService,
);
}

View File

@@ -0,0 +1 @@
<svg width="600" height="600" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M169.086 251.309c0 7.383.798 13.37 2.195 17.76 1.596 4.39 3.592 9.179 6.386 14.368.997 1.596 1.397 3.193 1.397 4.589 0 1.996-1.198 3.992-3.792 5.987l-12.572 8.381c-1.796 1.198-3.592 1.796-5.188 1.796-1.996 0-3.991-.997-5.987-2.793a61.826 61.826 0 0 1-7.184-9.379c-1.995-3.393-3.991-7.184-6.186-11.774-15.565 18.359-35.121 27.538-58.669 27.538-16.762 0-30.132-4.789-39.91-14.368-9.779-9.578-14.767-22.35-14.767-38.314 0-16.962 5.986-30.731 18.159-41.108 12.173-10.377 28.337-15.565 48.89-15.565 6.785 0 13.77.598 21.153 1.596 7.384.998 14.967 2.594 22.949 4.39v-14.567c0-15.166-3.193-25.742-9.379-31.929-6.386-6.186-17.162-9.179-32.527-9.179-6.985 0-14.169.798-21.552 2.594-7.384 1.796-14.567 3.991-21.552 6.785-3.193 1.397-5.587 2.195-6.984 2.594-1.397.399-2.395.599-3.193.599-2.794 0-4.19-1.996-4.19-6.186v-9.778c0-3.193.398-5.588 1.396-6.985.998-1.397 2.794-2.794 5.588-4.19 6.984-3.592 15.365-6.586 25.143-8.98 9.779-2.595 20.155-3.792 31.13-3.792 23.748 0 41.109 5.388 52.284 16.164 10.975 10.776 16.563 27.139 16.563 49.09v64.656h.399Zm-81.019 30.332c6.585 0 13.37-1.198 20.554-3.592 7.184-2.395 13.57-6.785 18.958-12.772 3.193-3.791 5.587-7.982 6.785-12.771 1.197-4.789 1.995-10.576 1.995-17.361v-8.382c-5.787-1.396-11.973-2.594-18.359-3.392-6.386-.798-12.572-1.197-18.758-1.197-13.37 0-23.148 2.594-29.733 7.982-6.586 5.388-9.779 12.971-9.779 22.949 0 9.379 2.395 16.363 7.384 21.152 4.79 4.989 11.774 7.384 20.953 7.384Zm160.242 21.552c-3.592 0-5.987-.599-7.583-1.996-1.597-1.197-2.994-3.991-4.191-7.783L189.64 139.159c-1.197-3.991-1.796-6.585-1.796-7.982 0-3.193 1.596-4.989 4.789-4.989h19.556c3.792 0 6.386.599 7.783 1.996 1.597 1.197 2.794 3.991 3.991 7.783l33.525 132.104 31.131-132.104c.997-3.992 2.195-6.586 3.791-7.783 1.597-1.197 4.39-1.996 7.982-1.996h15.965c3.791 0 6.385.599 7.982 1.996 1.596 1.197 2.993 3.991 3.791 7.783l31.53 133.7 34.522-133.7c1.198-3.992 2.595-6.586 3.992-7.783 1.596-1.197 4.19-1.996 7.782-1.996h18.559c3.192 0 4.988 1.597 4.988 4.989 0 .998-.199 1.996-.399 3.193-.199 1.197-.598 2.794-1.397 4.989l-48.092 154.255c-1.197 3.991-2.594 6.585-4.191 7.783-1.596 1.197-4.19 1.995-7.583 1.995H350.68c-3.792 0-6.386-.599-7.982-1.995-1.597-1.397-2.994-3.992-3.792-7.983l-30.931-128.712-30.731 128.513c-.998 3.991-2.195 6.585-3.792 7.982-1.596 1.397-4.39 1.996-7.982 1.996h-17.161Zm256.426 5.387c-10.377 0-20.753-1.197-30.731-3.591-9.978-2.395-17.76-4.989-22.949-7.983-3.193-1.796-5.388-3.791-6.186-5.587a14.091 14.091 0 0 1-1.197-5.588v-10.177c0-4.191 1.596-6.186 4.59-6.186 1.197 0 2.394.2 3.591.599 1.198.399 2.994 1.197 4.989 1.995a108.617 108.617 0 0 0 21.951 6.985c7.982 1.596 15.765 2.394 23.747 2.394 12.572 0 22.35-2.195 29.135-6.585 6.785-4.39 10.377-10.776 10.377-18.958 0-5.587-1.796-10.177-5.388-13.968-3.592-3.792-10.377-7.184-20.155-10.377l-28.935-8.98c-14.568-4.59-25.344-11.375-31.929-20.355-6.585-8.78-9.978-18.558-9.978-28.935 0-8.381 1.796-15.765 5.388-22.15 3.592-6.386 8.381-11.974 14.368-16.364 5.987-4.59 12.772-7.982 20.754-10.377 7.982-2.394 16.363-3.392 25.143-3.392 4.391 0 8.98.2 13.371.798 4.589.599 8.78 1.397 12.971 2.195 3.991.998 7.782 1.996 11.374 3.193 3.592 1.197 6.386 2.395 8.381 3.592 2.794 1.597 4.79 3.193 5.987 4.989 1.197 1.596 1.796 3.791 1.796 6.585v9.379c0 4.191-1.596 6.386-4.59 6.386-1.596 0-4.19-.798-7.583-2.395-11.374-5.188-24.146-7.782-38.314-7.782-11.375 0-20.355 1.796-26.541 5.587-6.186 3.792-9.379 9.579-9.379 17.761 0 5.587 1.996 10.376 5.987 14.168 3.991 3.791 11.374 7.583 21.951 10.975l28.336 8.98c14.368 4.59 24.745 10.976 30.931 19.157 6.186 8.182 9.18 17.561 9.18 27.938 0 8.581-1.796 16.363-5.189 23.148-3.592 6.785-8.381 12.772-14.567 17.561-6.186 4.989-13.57 8.581-22.151 11.175-8.98 2.794-18.359 4.19-28.536 4.19Z" fill="#252F3E"/><path fill-rule="evenodd" clip-rule="evenodd" d="M542.451 405.564c-65.653 48.491-161.04 74.234-243.057 74.234-114.943 0-218.51-42.505-296.736-113.147-6.186-5.588-.599-13.171 6.785-8.781 84.61 49.09 188.977 78.824 296.936 78.824 72.837 0 152.858-15.166 226.493-46.296 10.976-4.989 20.355 7.183 9.579 15.166Z" fill="#F90"/><path fill-rule="evenodd" clip-rule="evenodd" d="M569.79 374.433c-8.382-10.776-55.476-5.188-76.828-2.594-6.386.798-7.384-4.789-1.597-8.98 37.516-26.341 99.178-18.758 106.362-9.978 7.184 8.98-1.995 70.642-37.117 100.176-5.388 4.59-10.576 2.195-8.181-3.791 7.982-19.756 25.742-64.257 17.361-74.833Z" fill="#F90"/></svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -0,0 +1 @@
<svg width="600" height="600" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)" fill="#fff"><path d="M169.086 251.309c0 7.383.798 13.37 2.195 17.76 1.596 4.39 3.592 9.179 6.386 14.368.997 1.596 1.397 3.193 1.397 4.589 0 1.996-1.198 3.992-3.792 5.987l-12.572 8.381c-1.796 1.198-3.592 1.796-5.188 1.796-1.996 0-3.991-.997-5.987-2.793a61.826 61.826 0 0 1-7.184-9.379c-1.995-3.393-3.991-7.184-6.186-11.774-15.565 18.359-35.121 27.538-58.669 27.538-16.762 0-30.132-4.789-39.91-14.368-9.779-9.578-14.767-22.35-14.767-38.314 0-16.962 5.986-30.731 18.159-41.108 12.173-10.377 28.337-15.565 48.89-15.565 6.785 0 13.77.598 21.153 1.596 7.384.998 14.967 2.594 22.949 4.39v-14.567c0-15.166-3.193-25.742-9.379-31.929-6.386-6.186-17.162-9.179-32.527-9.179-6.985 0-14.169.798-21.552 2.594-7.384 1.796-14.567 3.991-21.552 6.785-3.193 1.397-5.587 2.195-6.984 2.594-1.397.399-2.395.599-3.193.599-2.794 0-4.19-1.996-4.19-6.186v-9.778c0-3.193.398-5.588 1.396-6.985.998-1.397 2.794-2.794 5.588-4.19 6.984-3.592 15.365-6.586 25.143-8.98 9.779-2.595 20.155-3.792 31.13-3.792 23.748 0 41.109 5.388 52.284 16.164 10.975 10.776 16.563 27.139 16.563 49.09v64.656h.399Zm-81.019 30.332c6.585 0 13.37-1.198 20.554-3.592 7.184-2.395 13.57-6.785 18.958-12.772 3.193-3.791 5.587-7.982 6.785-12.771 1.197-4.789 1.995-10.576 1.995-17.361v-8.382c-5.787-1.396-11.973-2.594-18.359-3.392-6.386-.798-12.572-1.197-18.758-1.197-13.37 0-23.148 2.594-29.733 7.982-6.586 5.388-9.779 12.971-9.779 22.949 0 9.379 2.395 16.363 7.384 21.152 4.79 4.989 11.774 7.384 20.953 7.384Zm160.242 21.552c-3.592 0-5.987-.599-7.583-1.996-1.597-1.197-2.994-3.991-4.191-7.783L189.64 139.159c-1.197-3.991-1.796-6.585-1.796-7.982 0-3.193 1.596-4.989 4.789-4.989h19.556c3.792 0 6.386.599 7.783 1.996 1.597 1.197 2.794 3.991 3.991 7.783l33.525 132.104 31.131-132.104c.997-3.992 2.195-6.586 3.791-7.783 1.597-1.197 4.39-1.996 7.982-1.996h15.965c3.791 0 6.385.599 7.982 1.996 1.596 1.197 2.993 3.991 3.791 7.783l31.53 133.7 34.522-133.7c1.198-3.992 2.595-6.586 3.992-7.783 1.596-1.197 4.19-1.996 7.782-1.996h18.559c3.192 0 4.988 1.597 4.988 4.989 0 .998-.199 1.996-.399 3.193-.199 1.197-.598 2.794-1.397 4.989l-48.092 154.255c-1.197 3.991-2.594 6.585-4.191 7.783-1.596 1.197-4.19 1.995-7.583 1.995H350.68c-3.792 0-6.386-.599-7.982-1.995-1.597-1.397-2.994-3.992-3.792-7.983l-30.931-128.712-30.731 128.513c-.998 3.991-2.195 6.585-3.792 7.982-1.596 1.397-4.39 1.996-7.982 1.996h-17.161Zm256.426 5.387c-10.377 0-20.753-1.197-30.731-3.591-9.978-2.395-17.76-4.989-22.949-7.983-3.193-1.796-5.388-3.791-6.186-5.587a14.091 14.091 0 0 1-1.197-5.588v-10.177c0-4.191 1.596-6.186 4.59-6.186 1.197 0 2.394.2 3.591.599 1.198.399 2.994 1.197 4.989 1.995a108.617 108.617 0 0 0 21.951 6.985c7.982 1.596 15.765 2.394 23.747 2.394 12.572 0 22.35-2.195 29.135-6.585 6.785-4.39 10.377-10.776 10.377-18.958 0-5.587-1.796-10.177-5.388-13.968-3.592-3.792-10.377-7.184-20.155-10.377l-28.935-8.98c-14.568-4.59-25.344-11.375-31.929-20.355-6.585-8.78-9.978-18.558-9.978-28.935 0-8.381 1.796-15.765 5.388-22.15 3.592-6.386 8.381-11.974 14.368-16.364 5.987-4.59 12.772-7.982 20.754-10.377 7.982-2.394 16.363-3.392 25.143-3.392 4.391 0 8.98.2 13.371.798 4.589.599 8.78 1.397 12.971 2.195 3.991.998 7.782 1.996 11.374 3.193 3.592 1.197 6.386 2.395 8.381 3.592 2.794 1.597 4.79 3.193 5.987 4.989 1.197 1.596 1.796 3.791 1.796 6.585v9.379c0 4.191-1.596 6.386-4.59 6.386-1.596 0-4.19-.798-7.583-2.395-11.374-5.188-24.146-7.782-38.314-7.782-11.375 0-20.355 1.796-26.541 5.587-6.186 3.792-9.379 9.579-9.379 17.761 0 5.587 1.996 10.376 5.987 14.168 3.991 3.791 11.374 7.583 21.951 10.975l28.336 8.98c14.368 4.59 24.745 10.976 30.931 19.157 6.186 8.182 9.18 17.561 9.18 27.938 0 8.581-1.796 16.363-5.189 23.148-3.592 6.785-8.381 12.772-14.567 17.561-6.186 4.989-13.57 8.581-22.151 11.175-8.98 2.794-18.359 4.19-28.536 4.19Z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M542.451 405.564c-65.653 48.491-161.04 74.234-243.057 74.234-114.943 0-218.51-42.505-296.736-113.147-6.186-5.588-.599-13.171 6.785-8.781 84.61 49.09 188.977 78.824 296.936 78.824 72.837 0 152.858-15.166 226.493-46.296 10.976-4.989 20.355 7.183 9.579 15.166Z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M569.79 374.433c-8.382-10.776-55.476-5.188-76.828-2.594-6.386.798-7.384-4.789-1.597-8.98 37.516-26.341 99.178-18.758 106.362-9.978 7.184 8.98-1.995 70.642-37.117 100.176-5.388 4.59-10.576 2.195-8.181-3.791 7.982-19.756 25.742-64.257 17.361-74.833Z"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h600v600H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 374.5 377.3" width="2481" height="2500"><path d="M290 166.3c.4 0 .8.5 1.4 1.4.5.8 42.6 51.3 93.6 112.2 51 60.9 92.6 111 92.4 111.3-.1.3-40.7 33.6-90.2 73.9s-91.6 74.6-93.5 76.2c-3.3 2.7-3.5 2.8-4.7 1.6-.7-.7-42.9-35.2-93.8-76.7S102.8 390.5 103 390c.2-.5 42-50.4 93.1-111s92.9-110.7 93.1-111.5c.2-.8.5-1.2.8-1.2z" style="fill:#00bef2" transform="translate(-102.969 -166.294)"/><path d="M283.1 483.6c-5.8-2.1-12.8-8.1-15.7-13.7-3.6-6.9-3.3-17.7.7-26.3 3.1-6.4 3.1-6.6 1.1-8.1-1.1-.8-14.4-8.2-29.4-16.3-15-8.1-28.1-15.2-29-15.7-1.2-.7-3.2 0-6.8 2.3-11.7 7.4-23.9 6.6-33.5-2.3-6.9-6.4-8.9-10.9-8.9-20.1 0-8.9 1.8-13.5 7.5-19.2 7.7-7.7 18-10.3 27.9-7 5.4 1.8 5.5 1.8 8.9-.8 4-3 36.1-32.3 51.6-47l10.7-10.2-3.2-6.7c-6.5-13.5-3.2-28.5 8.2-37.5 6.2-4.9 10.8-6.4 19.7-6.4 20.8 0 35.3 21.8 27.5 41.3-2.1 5.4-2.1 5.5-.1 8.8 1.7 2.9 30.6 37.8 45.9 55.6 2.7 3.1 5.7 5.6 6.7 5.6s4.4-1 7.6-2.2c14.9-5.9 30.6.7 36.8 15.5 4 9.5.5 22.3-8 30-6 5.4-10.4 7.1-18.4 7.1-5.6 0-7.7-.6-13.6-3.8-4.4-2.4-7.8-3.6-9.2-3.2-2.4.6-39.3 25.9-47.5 32.5-5 4.1-5.4 5.6-2.8 11.7 2.5 6 2.2 15.4-.6 21.3-3.1 6.5-10.8 13-17.5 15-6.8 1.9-10.9 1.9-16.6-.2zm1.7-110.2v-57l-3.2-4.4c-1.8-2.4-3.5-4.4-3.8-4.4-1.3 0-65.9 58.7-65.9 59.9 0 .3 1 3.3 2.2 6.5 1.2 3.3 2.1 8 2 10.7-.1 2.7-.1 5.7-.1 6.7.1 2.3 21.7 16.1 54.1 34.8 8.9 5.2 12 6.5 13.1 5.6 1.3-1.1 1.6-12.2 1.6-58.4zm27.4 50.4c42.8-26.9 50.8-32.3 51.3-34.3.3-1.2.7-5.9.8-10.6l.3-8.4-21.8-25.9c-23.4-27.7-32-37.1-34-37.1-.7 0-4.2 2-7.8 4.4l-6.6 4.4.3 56.9c.3 51 .7 59.6 2.6 59.6.2.1 7-4 14.9-9z" style="fill:#fff;stroke:#fff;stroke-width:1.2357;stroke-linecap:round;stroke-linejoin:round" transform="translate(-102.969 -166.294)"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@@ -0,0 +1 @@
<svg width="600" height="600" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M81.081 446.322C36.122 445.244 0 408.378 0 363.092c0-45.969 37.215-83.231 83.108-83.231a82.864 82.864 0 0 1 30.168 5.654c13.049-27.358 40.921-46.254 73.21-46.254 2.012 0 3.991.071 5.955.214C201.8 190.778 244.589 154 295.946 154c39.955 0 74.715 22.259 92.594 55.064 9.533-4.084 20.033-6.344 31.055-6.344 43.66 0 79.054 35.446 79.054 79.171a83.331 83.331 0 0 1 18.243-2.03c45.893 0 83.108 37.262 83.108 83.231 0 45.286-36.122 82.152-81.081 83.23h-20.175c-9.803-29.491-36.978-53.573-72.546-65.317 12.21-12.458 19.748-29.531 19.748-48.364 0-38.118-30.849-69.02-68.919-69.02-38.07 0-68.919 30.902-68.919 69.02 0 18.833 7.538 35.906 19.748 48.364-11.83 3.901-22.757 9.159-32.417 15.534-6.619 4.369-12.653 9.262-17.974 14.607-8.425-7.034-18.116-12.862-28.695-17.184 9.169-10.388 14.744-24.035 14.744-38.991 0-32.511-26.32-58.87-58.784-58.87s-58.784 26.359-58.784 58.87c0 14.956 5.574 28.603 14.743 38.991-25.163 10.277-45.196 29.086-55.584 52.36H81.081Z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg width="600" height="600" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M459.852 485.376 398.925 300l159.424-114.547H361.201L300.275 0h197.162l60.989 185.438c35.314 107.518-1.112 229.866-98.867 299.907l.293.031Zm-318.939 0L300.336 600l159.516-114.624-159.423-114.547-159.516 114.547Zm-98.558-300C5.079 298.811 48.349 418.95 140.867 485.422v-.046L201.794 300 42.37 185.376l197.117.062L300.275 0H103.004l-60.65 185.376Z" fill="#EB5424"/></svg>

After

Width:  |  Height:  |  Size: 455 B

View File

@@ -0,0 +1 @@
<svg width="600" height="600" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)"><path d="M96.958 396.5H.195v-91.924H193.6c-2.521 51.193-44.824 91.924-96.641 91.924Z" fill="#7BCD54"/><path d="M96.957 202.977H.195V294.9H193.6c-2.521-51.192-44.824-91.923-96.641-91.923ZM503.356 202.977c-51.817 0-94.121 40.731-96.641 91.923h193.282c-2.52-51.192-44.823-91.923-96.641-91.923Z" fill="#63C43F"/><path d="M503.356 396.5c-51.817 0-94.121-40.731-96.641-91.924h193.282c-2.52 51.193-44.823 91.924-96.641 91.924Z" fill="#7BCD54"/><path d="M203.395 202.977v96.762c0 51.817 40.731 94.12 91.924 96.641V202.977h-91.924Z" fill="#63C43F"/><path d="M396.922 396.501h-91.926V202.977h91.926v193.524Z" fill="#7BCD54"/></g><defs><clipPath id="a"><path fill="#fff" transform="translate(0 203)" d="M0 0h600v193.5H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 836 B

View File

@@ -0,0 +1 @@
<svg width="600" height="600" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M600 313.996c0-50.02-31.192-93.978-78.107-110.903a169.795 169.795 0 0 0 3.044-32.084C524.937 77.789 448.859 2 355.538 2c-54.777 0-105.496 26.02-137.449 69.978-15.723-12.126-34.994-18.694-55.028-18.694-49.705 0-90.026 40.168-90.026 89.683 0 10.863 2.028 21.474 5.579 31.326C31.953 190.967 0 235.682 0 285.449c0 50.273 31.445 94.231 78.36 111.158-2.028 10.358-3.043 21.22-3.043 32.082 0 92.969 75.825 168.504 169.147 168.504 54.776 0 105.494-26.272 137.193-70.231 15.723 12.379 34.996 19.2 55.03 19.2 49.703 0 90.025-40.167 90.025-89.683 0-10.863-2.029-21.473-5.579-31.327C567.794 408.48 600 363.765 600 313.996Z" fill="#fff"/><path d="m237.006 258.654 130.334 59.815 131.342-116.08c2.012-9.632 2.767-19.009 2.767-29.148 0-81.61-65.922-148.014-146.941-148.014-48.56 0-93.599 24.078-121.025 64.376L211.594 203.91l25.412 54.745Z" fill="#FED10A"/><path d="M98.438 397.96c-2.026 9.685-2.786 19.623-2.786 29.817 0 82.32 66.605 149.093 148.403 149.093 49.13 0 94.968-24.467 122.573-65.244l21.778-114.43-29.123-56.07-131.689-60.401L98.438 397.96Z" fill="#24BBB1"/><path d="m100.032 171.886 91.466 21.737 20.096-104.288c-12.367-9.575-27.825-14.75-43.8-14.75-39.678 0-72.142 32.347-72.142 72.458 0 8.539 1.546 17.08 4.38 24.843Z" fill="#EF5098"/><path d="M88.903 193.623c-40.301 13.109-68.614 51.433-68.614 93.538 0 41.096 25.762 77.655 64.278 92.278l127.026-113.456-23.212-49.164-99.478-23.196Z" fill="#1BA9F5"/><path d="M391.297 509.966c12.337 9.341 27.191 14.643 42.549 14.643 38.772 0 70.494-31.559 70.494-70.69 0-8.584-1.51-16.917-4.28-24.49l-89.124-20.955-19.639 101.492Z" fill="#93C83E"/><path d="m408.86 385.531 99.395 22.943c40.524-13.111 68.557-51.434 68.557-93.791 0-40.844-25.74-77.655-64.224-92.026l-129.979 112.7 26.251 50.174Z" fill="#07C"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1 @@
<svg width="600" height="600" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M593.251 306.75c0-19.75-1.75-38.5-4.75-56.75H306v112.75h161.75c-7.25 37-28.5 68.25-60 89.501v75h96.5c56.501-52.25 89.001-129.251 89.001-220.501Z" fill="#4285F4"/><path d="M306.001 600.001c81 0 148.75-27 198.25-72.751l-96.5-75c-27 18-61.25 29-101.75 29-78.251 0-144.501-52.75-168.251-124h-99.5v77.25c49.25 98 150.5 165.501 267.751 165.501Z" fill="#34A853"/><path d="M137.75 357.25c-6.25-18-9.5-37.25-9.5-57.25s3.5-39.25 9.5-57.25V165.5h-99.5C17.75 206 6 251.5 6 300s11.75 94 32.25 134.501l99.5-77.251Z" fill="#FBBC05"/><path d="M306.001 118.75c44.25 0 83.75 15.25 115 45l85.5-85.5C454.751 29.75 387.001 0 306.001 0 188.75 0 87.501 67.5 38.25 165.5l99.5 77.251c23.75-71.251 90-124.001 168.251-124.001Z" fill="#EA4335"/></svg>

After

Width:  |  Height:  |  Size: 809 B

View File

@@ -0,0 +1 @@
<svg width="600" height="600" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M81.081 446.322C36.122 445.244 0 408.378 0 363.092c0-45.969 37.215-83.231 83.108-83.231a82.864 82.864 0 0 1 30.168 5.654c13.049-27.358 40.921-46.254 73.21-46.254 2.012 0 3.991.071 5.955.214C201.8 190.778 244.589 154 295.946 154c39.955 0 74.715 22.259 92.594 55.064 9.533-4.084 20.033-6.344 31.055-6.344 43.66 0 79.054 35.446 79.054 79.171a83.331 83.331 0 0 1 18.243-2.03c45.893 0 83.108 37.262 83.108 83.231 0 45.286-36.122 82.152-81.081 83.23h-20.175c-9.803-29.491-36.978-53.573-72.546-65.317 12.21-12.458 19.748-29.531 19.748-48.364 0-38.118-30.849-69.02-68.919-69.02-38.07 0-68.919 30.902-68.919 69.02 0 18.833 7.538 35.906 19.748 48.364-11.83 3.901-22.757 9.159-32.417 15.534-6.619 4.369-12.653 9.262-17.974 14.607-8.425-7.034-18.116-12.862-28.695-17.184 9.169-10.388 14.744-24.035 14.744-38.991 0-32.511-26.32-58.87-58.784-58.87s-58.784 26.359-58.784 58.87c0 14.956 5.574 28.603 14.743 38.991-25.163 10.277-45.196 29.086-55.584 52.36H81.081Z" fill="#002B49"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,23 @@
<svg width="600" height="600" viewBox="0 0 600 600" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_8150_20)">
<path d="M78.6386 163.123L156.203 29.0543L468.552 29L545.709 164.427L545.804 435.309L468.579 570.628L156.312 570.737L77.8516 435.309L78.6386 163.123Z" fill="#4D4D4D"/>
<path d="M77.8496 435.268H218.636L141.302 298.808L204.184 163.149L78.6367 163.122L0 299.909" fill="#EDEDED"/>
<path d="M261.325 435.269H364.21L455.209 302.451L365.798 164.428H244.742L171.832 297.803L261.325 435.269Z" fill="#E0E0E0"/>
<path d="M0 299.896L77.8632 435.309H218.636L142.157 300.358L0 299.896Z" fill="#ACACAC"/>
<path d="M173.34 299.923L261.326 435.323H364.212L453.786 299.977L173.34 299.923Z" fill="#9E9E9E"/>
<path d="M208.188 300.017L181.619 307.75L156.148 299.977L260.188 119.389L286.214 164.454" fill="#00B8E3"/>
<path d="M286.119 435.282L260.214 480.564L191.104 405.994L156.094 300.031V299.977H208.174" fill="#33C6E9"/>
<path d="M156.146 299.977H156.106V300.017L130.079 345.164L103.957 300.221L130.445 254.204L208.173 119.389H260.199" fill="#008AAA"/>
<path d="M442.443 435.269H600.002L599.907 164.414H442.43L442.443 435.269Z" fill="#D4D4D4"/>
<path d="M442.441 300.357V435.309H599.715V300.357H442.441Z" fill="#919191"/>
<path d="M260.226 480.606H208.172L130.078 345.165L156.105 300.031L260.226 480.606Z" fill="#00B8E3"/>
<path d="M468.4 299.977L364.334 480.578C354.767 466.499 338.375 435.309 338.375 435.309L416.442 299.963L468.4 299.977Z" fill="#008AAA"/>
<path d="M416.322 480.578L364.336 480.565L468.416 299.977L494.429 254.898L520.496 300.289M468.416 299.977H416.457L338.363 164.441L364.214 119.402L427.422 199.884L468.416 299.977Z" fill="#00B8E3"/>
<path d="M494.415 254.843V254.884L468.402 299.977L364.227 119.416L416.348 119.443L494.415 254.843Z" fill="#33C6E9"/>
</g>
<defs>
<clipPath id="clip0_8150_20">
<rect width="600" height="600" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1 @@
<svg width="600" height="600" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M126.732 467.734c12.934 8.066 34.434 17.033 57.167 17.033 20.7 0 39.933-6 55.867-16.233 0 0 .033 0 .066-.034l60.167-37.6v135.767c-9.533 0-19.133-2.6-27.467-7.8l-145.8-91.133Z" fill="#225086"/><path d="m261.767 50.234-250 282c-19.3 21.8-14.267 54.733 10.766 70.367 0 0 92.534 57.833 104.2 65.133 12.934 8.067 34.434 17.033 57.167 17.033 20.7 0 39.933-6 55.867-16.233 0 0 .033 0 .066-.033l60.167-37.6-145.467-90.934 145.5-164.133v-142.5c-14.133 0-28.266 5.633-38.266 16.9Z" fill="#6DF"/><path d="m154.533 339.967 1.734 1.067L300 430.901h.033V175.867l-.033-.033-145.467 164.133Z" fill="#CBF8FF"/><path d="M577.468 402.6c25.034-15.633 30.067-48.567 10.767-70.367L424.202 147.2a103.399 103.399 0 0 0-43.767-9.7c-30.833 0-58.4 13.3-76.733 34.2l-3.634 4.1 145.467 164.133-145.5 90.934v135.766c9.567 0 19.1-2.6 27.433-7.8l250-156.266v.033Z" fill="#074793"/><path d="M300.033 33.334v142.5l3.634-4.1c18.333-20.9 45.9-34.2 76.733-34.2 15.733 0 30.533 3.567 43.767 9.7L338.2 50.267c-9.967-11.266-24.1-16.9-38.2-16.9l.033-.033Z" fill="#0294E4"/><path d="m445.502 339.967-145.467-164.1v255l145.467-90.9Z" fill="#96BCC2"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1 @@
<svg width="600" height="600" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)" fill="#1976D2"><path d="M271.429 371.286V357h185.714v28.571l71.428-50-71.428-50v28.572H42.857V99.857H400V157h42.857V78.429c0-11.858-9.571-21.429-21.428-21.429h-400C9.57 57 0 66.571 0 78.429V335.57C0 347.429 9.571 357 21.429 357H200v14.286c0 23.714-19.143 42.857-42.857 42.857h-42.857V457h242.857v-42.857h-42.857c-23.715 0-42.857-19.143-42.857-42.857Z"/><path d="M578.572 185.571H280.715c-8-16.857-25.143-28.571-45-28.571-27.572 0-50 22.429-50 50s22.428 50 50 50c19.857 0 37-11.714 45-28.571h276.428v242.857H428.572v-85.715h-42.857v135.715c0 11.857 9.571 21.428 21.428 21.428h171.429c11.857 0 21.429-9.571 21.429-21.428V207c0-11.857-9.572-21.429-21.429-21.429ZM471.429 514.143v-14.286h42.857v14.286h-42.857Z"/></g><defs><clipPath id="a"><path fill="#fff" transform="translate(0 57)" d="M0 0h600v485.714H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 931 B

View File

@@ -0,0 +1 @@
<svg width="600" height="600" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)"><path d="M547.991 279.316c0 162.64-194.461 289.924-236.888 318.209-3.536 3.536-10.607 3.536-17.679 0C250.996 572.776 53 441.956 53 279.316V84.856c0-7.072 7.071-14.143 14.143-14.143C219.176 67.177 187.355 0 300.496 0c113.14 0 77.784 67.177 229.817 70.713 7.071 0 14.143 7.071 14.143 14.143l3.535 194.46Z" fill="#1B93EB"/><path d="M526.778 279.316c0 148.498-180.318 268.71-219.21 289.924-3.536 3.535-10.607 3.535-14.143 0-38.892-24.75-219.21-144.962-219.21-289.924V102.534c0-7.071 3.535-14.143 10.607-17.678C222.712 81.32 190.89 21.214 296.96 21.214c106.069 0 74.248 63.642 212.139 63.642 7.071 0 14.143 7.07 14.143 14.142l3.535 180.318Z" fill="url(#b)"/><path d="M300.495 212.139c49.499 0 91.927 38.892 98.998 88.391 0 7.071 10.607 14.143 17.679 14.143h49.499c10.607 0 17.678-7.072 17.678-17.679v-3.535c-10.607-102.534-102.534-176.783-205.068-166.176-88.391 10.607-155.569 77.784-166.176 166.176 0 10.607 7.072 17.678 14.143 17.678h53.035c7.071 0 14.142-7.071 17.678-14.143 10.607-49.499 53.035-84.855 102.534-84.855Z" fill="#C3F1FF"/><path d="M300.496 378.315c37.101 0 67.177-30.077 67.177-67.178s-30.076-67.177-67.177-67.177c-37.101 0-67.178 30.076-67.178 67.177 0 37.101 30.077 67.178 67.178 67.178Z" fill="#fff"/></g><defs><linearGradient id="b" x1="300.497" y1="552.622" x2="300.497" y2="2.121" gradientUnits="userSpaceOnUse"><stop stop-color="#1B93EB"/><stop offset=".21" stop-color="#2095EB"/><stop offset=".44" stop-color="#2E9CED"/><stop offset=".69" stop-color="#45A7EF"/><stop offset=".95" stop-color="#64B6F1"/><stop offset="1" stop-color="#6BB9F2"/></linearGradient><clipPath id="a"><path fill="#fff" transform="translate(53)" d="M0 0h494.991v600H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1 @@
<svg width="600" height="600" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="m330.104 4.167-12.342 152c-5.837-.667-11.675-1-17.679-1-7.505 0-14.843.5-22.015 1.666l-7.004-73.666c-.167-2.334 1.668-4.334 4.002-4.334h12.509l-6.004-74.5C281.404 2 283.239 0 285.407 0h40.861c2.335 0 4.169 2 3.836 4.333v-.166Zm-103.07 7.5c-.667-2.167-3.002-3.5-5.17-2.667l-38.36 14c-2.168.833-3.169 3.333-2.168 5.333l31.188 68-11.841 4.334c-2.169.833-3.169 3.333-2.169 5.333l31.855 66.833a143.073 143.073 0 0 1 37.192-14.166l-40.36-147h-.167ZM132.97 54l88.227 124.333c-11.175 7.334-21.348 16-30.021 26l-52.869-52c-1.668-1.666-1.501-4.333.167-5.833l9.673-8-52.536-53.167c-1.668-1.666-1.5-4.333.334-5.833l31.188-26.167c1.834-1.5 4.336-1.166 5.67.667h.167Zm-74.05 72c-1.835-1.333-4.504-.667-5.67 1.333l-20.348 35.334c-1.168 2-.334 4.5 1.668 5.5l67.712 32L95.945 211c-1.168 2-.334 4.667 1.834 5.5l67.379 30.833c4.837-12.5 11.341-24.166 19.347-34.666L58.92 126ZM9.052 222c.334-2.333 2.669-3.667 4.837-3.167l147.6 38.5c-3.836 12.5-6.004 25.667-6.337 39.334l-73.884-6A3.85 3.85 0 0 1 77.766 286l2.168-12.333-74.55-7C3.047 266.5 1.546 264.333 1.88 262l7.005-40.167.166.167ZM3.55 321.667c-2.335.166-3.836 2.333-3.503 4.666L7.218 366.5c.333 2.333 2.668 3.667 4.836 3.167l72.383-18.834 2.168 12.334c.334 2.333 2.668 3.666 4.837 3.166l71.382-19.666c-4.17-12.334-6.838-25.5-7.506-39l-151.936 14h.167ZM27.23 427.333c-1.167-2-.333-4.5 1.668-5.5L166.66 356.5c5.171 12.333 12.175 23.833 20.514 34.167l-60.374 43c-1.835 1.333-4.503.833-5.67-1.167l-6.338-11L53.249 464c-1.835 1.333-4.503.667-5.67-1.333l-20.514-35.334h.166Zm166.947-28.666-107.24 108.5c-1.667 1.666-1.5 4.333.334 5.833l31.355 26.167c1.834 1.5 4.336 1.166 5.67-.667l43.363-61 9.673 8.167c1.835 1.5 4.503 1.166 5.838-.834l42.028-61c-11.341-7-21.848-15.5-30.854-25.166h-.167Zm-21.181 174.166c-2.168-.833-3.169-3.333-2.168-5.333l63.543-138.667c11.675 6 24.35 10.5 37.526 13l-18.68 71.667c-.5 2.167-3.002 3.5-5.17 2.667l-11.841-4.334L216.36 584c-.667 2.167-3.002 3.5-5.17 2.667l-38.36-14 .167.166Zm109.408-129.166-12.342 152c-.167 2.333 1.668 4.333 3.836 4.333h40.861c2.335 0 4.17-2 3.836-4.333l-6.004-74.5H325.1c2.335 0 4.17-2 4.003-4.334l-7.005-73.666c-7.171 1.166-14.509 1.666-22.015 1.666-6.004 0-11.841-.333-17.678-1.166Zm147.1-411.5c1-2.167 0-4.5-2.168-5.334l-38.36-14c-2.168-.833-4.503.5-5.17 2.667L363.96 87.667l-11.841-4.334c-2.168-.833-4.503.5-5.17 2.667l-18.68 71.667c13.343 2.666 25.851 7.166 37.526 13l63.71-138.5Zm83.723 60.5-107.239 108.5A145.541 145.541 0 0 0 375.134 176l42.029-61c1.334-1.833 4.003-2.333 5.837-.833l9.673 8.166 43.363-61c1.335-1.833 4.003-2.166 5.671-.666l31.354 26.166c1.835 1.5 1.835 4.167.334 5.834h-.167ZM571.268 178c2.168-1 2.835-3.5 1.667-5.5l-20.513-35.333c-1.168-2-3.836-2.5-5.671-1.334l-61.542 42.5-6.337-10.833c-1.168-2-3.836-2.667-5.671-1.167l-60.374 43c8.339 10.334 15.177 21.834 20.514 34.167l137.76-65.333.167-.167Zm21.681 55.333 7.005 40.167c.333 2.333-1.168 4.333-3.503 4.667l-151.936 14.166c-.667-13.666-3.336-26.666-7.505-39l71.382-19.666c2.168-.667 4.503.833 4.836 3.166l2.168 12.334 72.383-18.834c2.168-.5 4.503.834 4.836 3.167l.334-.167Zm-6.838 147.5c2.168.5 4.503-.833 4.837-3.166l7.004-40.167c.334-2.333-1.167-4.333-3.502-4.667l-74.551-7 2.169-12.333c.333-2.333-1.168-4.333-3.503-4.667l-73.883-6c-.334 13.667-2.502 26.834-6.338 39.334l147.6 38.5.167.166Zm-39.36 91.667c-1.167 2-3.836 2.5-5.671 1.333l-125.585-86.666c8.006-10.5 14.51-22.167 19.347-34.667l67.379 30.833c2.168 1 3.002 3.5 1.834 5.5l-6.337 10.834 67.712 32c2.002 1 2.836 3.5 1.668 5.5L546.751 472.5Zm-167.947-51.167 88.226 124.334c1.334 1.833 4.003 2.166 5.671.666l31.188-26.166c1.834-1.5 1.834-4.167.333-5.834l-52.536-53.166 9.674-8c1.834-1.5 1.834-4.167.166-5.834l-52.869-52c-8.839 10-18.846 18.834-30.02 26h.167Zm-.834 169.334c-2.168.833-4.503-.5-5.171-2.667l-40.36-147a143.04 143.04 0 0 0 37.192-14.167l31.855 66.834c1 2.166 0 4.666-2.168 5.333l-11.842 4.333 31.188 68c1.001 2.167 0 4.5-2.168 5.334l-38.36 14h-.166Z" fill="#191919"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h600v600H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -0,0 +1 @@
<svg width="600" height="600" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)"><circle cx="300" cy="300" r="300" fill="#1C1F2A"/><path d="M229.85 204.478a8.955 8.955 0 0 0-8.955 8.955v53.731a8.955 8.955 0 0 0 8.955 8.955h43.283V405.97a8.955 8.955 0 0 0 8.956 8.955h53.731a8.955 8.955 0 0 0 8.955-8.955V213.433a8.955 8.955 0 0 0-8.955-8.955H229.85Z" fill="#fff"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h600v600H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 473 B

View File

@@ -0,0 +1 @@
<svg width="600" height="600" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="600" height="600" rx="300" fill="#192FFF"/><path d="m445.835 230.057-23.98 44.93a4.887 4.887 0 0 0-.543 3.076l8.728 66.101c.415 3.108-.927 6.215-3.485 7.996l-49.686 35.154a5.108 5.108 0 0 0-1.726 2.137l-24.395 57.199c-1.376 3.205-4.285 5.471-7.706 5.989l-42.076 6.215a12.657 12.657 0 0 1-3.837 0l-42.076-6.215c-3.421-.518-6.331-2.784-7.706-5.989l-24.395-57.199c-.351-.874-.959-1.586-1.726-2.137l-49.686-35.122a8.427 8.427 0 0 1-3.485-7.996l8.729-66.101a5.357 5.357 0 0 0-.544-3.075l-24.043-44.963a10.537 10.537 0 0 1-1.183-5.406l1.151-19.682c.223-3.625 2.302-6.83 5.531-8.448l13.684-6.863a12.336 12.336 0 0 1 12.15.615l30.726 19.455a4.928 4.928 0 0 0 4.092.55l76.799-23.695a13.132 13.132 0 0 1 7.737 0l76.799 23.695a4.93 4.93 0 0 0 4.093-.55l30.725-19.455a12.336 12.336 0 0 1 12.15-.615l13.652 6.863a10.15 10.15 0 0 1 5.532 8.448l1.151 19.682c.095 1.877-.288 3.755-1.184 5.406h.033Z" fill="#F7F9FC"/></svg>

After

Width:  |  Height:  |  Size: 996 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -0,0 +1 @@
<svg width="600" height="600" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M554.306 249h-80.082c1.214 1.237 2.429 2.518 3.533 3.865 8.967 10.866 13.693 24.515 13.693 39.533 0 23.19-9.761 44.105-25.442 58.196h49.03l23.3-57.909h-29.153l4.859-22.461h57.909l-30.412 80.37h11.926c27.761.177 46.534-22.881 46.534-50.885 0-28.005-17.889-50.709-45.717-50.709" fill="#E85E26"/><path fill-rule="evenodd" clip-rule="evenodd" d="M67.887 249c22.397 0 35.208 12.19 35.208 28.798 0 18.112-11.906 33.262-30.79 36.221l15.753 36.575H62.72l-14.77-34.897H33.085l-7.687 34.897H0L22.31 249h45.577Zm-30.169 45.431h24.674c9.714 0 15.417-6.471 15.417-14.248 0-2.334-.69-4.378-1.993-5.996-1.993-2.467-5.4-3.943-9.939-3.943h-22.81l-5.349 24.187ZM99.385 350.594 158.395 249h29.997l22.241 101.594h-25.338l-3.71-16.853h-46.992l-9.878 16.853h-25.33Zm69.303-75.488-21.776 37.455h30.014l-8.238-37.455ZM245.375 249h42.954c21.646.022 35.001 12.035 35.001 29.177 0 19.234-14.658 37.477-38.866 37.477H256.15l-7.73 34.94h-25.355L245.375 249Zm15.46 45.431h22.525c9.007 0 14.865-6.471 14.865-14.248 0-6.25-4.754-9.939-12.484-9.939h-19.609l-5.297 24.187Z" fill="#000"/><path d="M372.41 249h-25.398l-22.353 101.594h25.398L372.41 249Z" fill="#000"/><path fill-rule="evenodd" clip-rule="evenodd" d="M423.759 249c16.961 0 31.334 5.961 40.039 16.564 5.849 7.001 9.214 16.099 9.214 26.835 0 17.224-6.738 37.503-25.735 49.028-9.343 5.634-21.577 9.167-37.477 9.167h-40.97L391.2 249h32.559Zm-24.872 80.393h14.028c21.861 0 34.94-16.302 34.94-34.63 0-13.627-8.153-24.519-22.681-24.519h-13.251l-13.036 59.149Z" fill="#000"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1 @@
<svg width="600" height="600" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M64.03 318.824c0 3.985-.84 7.658-2.54 11.038-1.68 3.38-4.043 6.232-7.051 8.596s-6.602 4.201-10.763 5.529c-4.16 1.329-8.712 1.973-13.654 1.973-5.918 0-11.212-.801-15.92-2.422-4.707-1.622-9.395-4.298-14.102-8.03l7.774-12.64c3.731 3.126 7.09 5.412 10.118 6.857 3.009 1.446 6.075 2.169 9.22 2.169 3.868 0 6.954-.977 9.317-2.989 2.364-2.013 3.516-4.689 3.516-8.069 0-1.446-.215-2.813-.644-4.083-.43-1.27-1.192-2.559-2.344-3.908-1.133-1.328-2.696-2.774-4.708-4.337-1.973-1.563-4.532-3.497-7.676-5.802-2.403-1.7-4.766-3.439-7.032-5.236a50.634 50.634 0 0 1-6.231-5.802 26.532 26.532 0 0 1-4.512-6.877c-1.133-2.54-1.72-5.432-1.72-8.675 0-3.731.782-7.17 2.345-10.237a23.325 23.325 0 0 1 6.406-7.854c2.696-2.188 5.939-3.868 9.747-5.06 3.79-1.192 7.892-1.817 12.365-1.817 4.707 0 9.24.645 13.634 1.876 4.395 1.27 8.458 3.087 12.208 5.49l-7.051 11.37c-4.825-3.38-9.884-5.06-15.197-5.06-3.242 0-5.938.84-8.048 2.54-2.129 1.699-3.164 3.809-3.164 6.349s.918 4.591 2.793 6.526c1.856 1.934 5.06 4.649 9.63 8.146 4.59 3.38 8.419 6.35 11.447 8.89 3.027 2.539 5.41 4.923 7.168 7.17 1.758 2.246 2.97 4.493 3.633 6.798.665 2.306.997 4.826.997 7.6h.058l-.02-.019Zm105.264-22.233c0 7.111-1.074 13.656-3.282 19.634-2.187 5.979-5.196 11.175-9.083 15.649-3.887 4.474-8.457 7.932-13.79 10.394-5.313 2.462-11.056 3.692-17.248 3.692-2.774 0-5.371-.234-7.794-.722a25.875 25.875 0 0 1-6.895-2.443c-2.168-1.133-4.317-2.618-6.446-4.434-2.129-1.817-4.317-4.045-6.621-6.702V389H74.89V253.356h23.245l.117 13.207c4.238-5.295 8.829-9.202 13.751-11.664 4.962-2.481 10.704-3.692 17.228-3.692 5.919 0 11.33 1.113 16.193 3.341 4.903 2.227 9.122 5.333 12.677 9.319 3.555 3.985 6.31 8.733 8.243 14.301 1.934 5.548 2.891 11.702 2.891 18.442h.039l.02-.019Zm-25.276.938c0-9.906-1.992-17.72-6.016-23.464-4.005-5.744-9.454-8.596-16.389-8.596-7.285 0-13.067 3.047-17.306 9.143-4.239 6.095-6.387 14.34-6.387 24.714 0 10.374 2.109 18.111 6.27 23.913 4.16 5.802 9.942 8.694 17.228 8.694 4.356 0 7.989-1.114 10.841-3.341 2.852-2.247 5.157-5.079 6.915-8.518 1.758-3.438 3.008-7.189 3.731-11.214.722-4.044 1.093-7.815 1.093-11.331h-.019.039Zm35.609 46.243h23.85V211h-23.85v132.772Zm125.735.039v-90.572h-23.85v48.744c0 4.337-.176 7.697-.547 10.061a25.839 25.839 0 0 1-1.817 6.448c-3.379 7.482-9.551 11.233-18.537 11.233-7.012 0-11.934-2.54-14.728-7.619-1.093-1.915-1.855-4.103-2.285-6.525-.43-2.423-.645-5.92-.645-10.531v-51.811h-23.85v51.46c0 3.497.039 6.466.098 8.889.059 2.422.215 4.552.469 6.447.234 1.876.527 3.536.82 4.982a19.198 19.198 0 0 0 1.368 4.161c2.187 5.568 5.703 9.769 10.606 12.601 4.903 2.833 10.9 4.24 18.01 4.24 6.407 0 12.013-1.114 16.857-3.341 4.844-2.247 9.532-5.88 14.142-10.96l.058 12.113h23.85v-.039h-.019v.019Zm102.881-.039v-51.343c0-3.497-.039-6.486-.098-8.947-.058-2.482-.195-4.592-.468-6.428a41.409 41.409 0 0 0-.899-4.787 42.825 42.825 0 0 0-1.27-4.083c-2.187-5.411-5.703-9.592-10.606-12.562-4.903-2.969-10.9-4.435-18.049-4.435-6.407 0-12.013 1.114-16.857 3.361-4.844 2.246-9.532 5.9-14.142 10.96l-.039-12.113H321.75v90.377h24.065v-48.783c0-4.22.156-7.502.469-9.847.312-2.344.879-4.532 1.738-6.603 1.563-3.615 3.965-6.369 7.188-8.225 3.223-1.856 7.052-2.794 11.545-2.794 7.012 0 11.934 2.54 14.728 7.58 1.074 1.915 1.836 4.103 2.265 6.506.43 2.403.645 5.9.645 10.491v51.695h23.85v-.02Zm98.779-5.958-36.351-45.209 30.745-32.919-18.088-7.776-31.995 37.061h-2.539V211h-24.065v132.753h24.065v-48.1l35.98 49.917 22.248-7.776h.02l-.02.02ZM600 305.363v-14.809l-72.331-36.299v16.254l56.06 27.293-56.06 27.645v15.903L600 305.402v-.039Z" fill="#000"/></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1 @@
<svg width="600" height="600" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)"><mask id="b" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="600" height="600"><path d="M600 0H0v600h600V0Z" fill="#fff"/></mask><g mask="url(#b)"><path fill-rule="evenodd" clip-rule="evenodd" d="m330.104 4.167-12.342 152c-5.837-.667-11.675-1-17.679-1-7.505 0-14.843.5-22.015 1.666l-7.004-73.666c-.167-2.334 1.668-4.334 4.002-4.334h12.509l-6.004-74.5C281.404 2 283.239 0 285.407 0h40.861c2.335 0 4.169 2 3.836 4.333v-.166Zm-103.07 7.5c-.667-2.167-3.002-3.5-5.17-2.667l-38.36 14c-2.168.833-3.169 3.333-2.168 5.333l31.188 68-11.841 4.334c-2.169.833-3.169 3.333-2.169 5.333l31.855 66.833a143.073 143.073 0 0 1 37.192-14.166l-40.36-147h-.167ZM132.97 54l88.227 124.333c-11.175 7.334-21.348 16-30.021 26l-52.869-52c-1.668-1.666-1.501-4.333.167-5.833l9.673-8-52.536-53.167c-1.668-1.666-1.5-4.333.334-5.833l31.188-26.167c1.834-1.5 4.336-1.166 5.67.667h.167Zm-74.05 72c-1.835-1.333-4.504-.667-5.67 1.333l-20.348 35.334c-1.168 2-.334 4.5 1.668 5.5l67.712 32L95.945 211c-1.168 2-.334 4.667 1.834 5.5l67.379 30.833c4.837-12.5 11.341-24.166 19.347-34.666L58.92 126ZM9.052 222c.334-2.333 2.669-3.667 4.837-3.167l147.6 38.5c-3.836 12.5-6.004 25.667-6.337 39.334l-73.884-6A3.85 3.85 0 0 1 77.766 286l2.168-12.333-74.55-7C3.047 266.5 1.546 264.333 1.88 262l7.005-40.167.166.167ZM3.55 321.667c-2.335.166-3.836 2.333-3.503 4.666L7.218 366.5c.333 2.333 2.668 3.667 4.836 3.167l72.383-18.834 2.168 12.334c.334 2.333 2.668 3.666 4.837 3.166l71.382-19.666c-4.17-12.334-6.838-25.5-7.506-39l-151.936 14h.167ZM27.23 427.333c-1.167-2-.333-4.5 1.668-5.5L166.66 356.5c5.171 12.333 12.175 23.833 20.514 34.167l-60.374 43c-1.835 1.333-4.503.833-5.67-1.167l-6.338-11L53.249 464c-1.835 1.333-4.503.667-5.67-1.333l-20.514-35.334h.166Zm166.947-28.666-107.24 108.5c-1.667 1.666-1.5 4.333.334 5.833l31.355 26.167c1.834 1.5 4.336 1.166 5.67-.667l43.363-61 9.673 8.167c1.835 1.5 4.503 1.166 5.838-.834l42.028-61c-11.341-7-21.848-15.5-30.854-25.166h-.167Zm-21.181 174.166c-2.168-.833-3.169-3.333-2.168-5.333l63.543-138.667c11.675 6 24.35 10.5 37.526 13l-18.68 71.667c-.5 2.167-3.002 3.5-5.17 2.667l-11.841-4.334L216.36 584c-.667 2.167-3.002 3.5-5.17 2.667l-38.36-14 .167.166Zm109.408-129.166-12.342 152c-.167 2.333 1.668 4.333 3.836 4.333h40.861c2.335 0 4.17-2 3.836-4.333l-6.004-74.5H325.1c2.335 0 4.17-2 4.003-4.334l-7.005-73.666c-7.171 1.166-14.509 1.666-22.015 1.666-6.004 0-11.841-.333-17.678-1.166Zm147.1-411.5c1-2.167 0-4.5-2.168-5.334l-38.36-14c-2.168-.833-4.503.5-5.17 2.667L363.96 87.667l-11.841-4.334c-2.168-.833-4.503.5-5.17 2.667l-18.68 71.667c13.343 2.666 25.851 7.166 37.526 13l63.71-138.5Zm83.723 60.5-107.239 108.5A145.541 145.541 0 0 0 375.134 176l42.029-61c1.334-1.833 4.003-2.333 5.837-.833l9.673 8.166 43.363-61c1.335-1.833 4.003-2.166 5.671-.666l31.354 26.166c1.835 1.5 1.835 4.167.334 5.834h-.167ZM571.268 178c2.168-1 2.835-3.5 1.667-5.5l-20.513-35.333c-1.168-2-3.836-2.5-5.671-1.334l-61.542 42.5-6.337-10.833c-1.168-2-3.836-2.667-5.671-1.167l-60.374 43c8.339 10.334 15.177 21.834 20.514 34.167l137.76-65.333.167-.167Zm21.681 55.333 7.005 40.167c.333 2.333-1.168 4.333-3.503 4.667l-151.936 14.166c-.667-13.666-3.336-26.666-7.505-39l71.382-19.666c2.168-.667 4.503.833 4.836 3.166l2.168 12.334 72.383-18.834c2.168-.5 4.503.834 4.836 3.167l.334-.167Zm-6.838 147.5c2.168.5 4.503-.833 4.837-3.166l7.004-40.167c.334-2.333-1.167-4.333-3.502-4.667l-74.551-7 2.169-12.333c.333-2.333-1.168-4.333-3.503-4.667l-73.883-6c-.334 13.667-2.502 26.834-6.338 39.334l147.6 38.5.167.166Zm-39.36 91.667c-1.167 2-3.836 2.5-5.671 1.333l-125.585-86.666c8.006-10.5 14.51-22.167 19.347-34.667l67.379 30.833c2.168 1 3.002 3.5 1.834 5.5l-6.337 10.834 67.712 32c2.002 1 2.836 3.5 1.668 5.5L546.751 472.5Zm-167.947-51.167 88.226 124.334c1.334 1.833 4.003 2.166 5.671.666l31.188-26.166c1.834-1.5 1.834-4.167.333-5.834l-52.536-53.166 9.674-8c1.834-1.5 1.834-4.167.166-5.834l-52.869-52c-8.839 10-18.846 18.834-30.02 26h.167Zm-.834 169.334c-2.168.833-4.503-.5-5.171-2.667l-40.36-147a143.04 143.04 0 0 0 37.192-14.167l31.855 66.834c1 2.166 0 4.666-2.168 5.333l-11.842 4.333 31.188 68c1.001 2.167 0 4.5-2.168 5.334l-38.36 14h-.166Z" fill="#fff"/></g></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h600v600H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1 @@
<svg width="600" height="600" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)"><mask id="b" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="600" height="600"><path d="M600 0H0v600h600V0Z" fill="#fff"/></mask><g mask="url(#b)"><path d="M300 600c165.685 0 300-134.315 300-300S465.685 0 300 0 0 134.315 0 300s134.315 300 300 300Z" fill="#fff"/><path d="M229.85 204.478a8.955 8.955 0 0 0-8.955 8.955v53.731a8.955 8.955 0 0 0 8.955 8.955h43.283V405.97a8.955 8.955 0 0 0 8.956 8.955h53.731a8.955 8.955 0 0 0 8.955-8.955V213.433a8.955 8.955 0 0 0-8.955-8.955H229.85Z" fill="#1C1F2A"/></g></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h600v600H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 718 B

View File

@@ -0,0 +1 @@
<svg width="600" height="600" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)"><path d="M554.306 249h-80.082c1.214 1.237 2.429 2.518 3.533 3.865 8.967 10.866 13.693 24.515 13.693 39.533 0 23.19-9.761 44.105-25.442 58.196h49.03l23.3-57.909h-29.153l4.859-22.461h57.909l-30.412 80.37h11.926c27.761.177 46.534-22.881 46.534-50.885 0-28.005-17.889-50.709-45.717-50.709" fill="#E85E26"/><path fill-rule="evenodd" clip-rule="evenodd" d="M67.887 249c22.397 0 35.208 12.19 35.208 28.798 0 18.112-11.906 33.262-30.79 36.221l15.753 36.575H62.72l-14.77-34.897H33.085l-7.687 34.897H0L22.31 249h45.577Zm-30.169 45.431h24.674c9.714 0 15.417-6.471 15.417-14.248 0-2.334-.69-4.378-1.993-5.996-1.993-2.467-5.4-3.943-9.939-3.943h-22.81l-5.349 24.187ZM99.385 350.594 158.395 249h29.997l22.241 101.594h-25.338l-3.71-16.853h-46.992l-9.878 16.853h-25.33Zm69.303-75.488-21.776 37.455h30.014l-8.238-37.455ZM245.375 249h42.954c21.646.022 35.001 12.035 35.001 29.177 0 19.234-14.658 37.477-38.866 37.477H256.15l-7.73 34.94h-25.355L245.375 249Zm15.46 45.431h22.525c9.007 0 14.865-6.471 14.865-14.248 0-6.25-4.754-9.939-12.484-9.939h-19.609l-5.297 24.187Z" fill="#fff"/><path d="M372.41 249h-25.398l-22.353 101.594h25.398L372.41 249Z" fill="#fff"/><path fill-rule="evenodd" clip-rule="evenodd" d="M423.759 249c16.961 0 31.334 5.961 40.039 16.564 5.849 7.001 9.214 16.099 9.214 26.835 0 17.224-6.738 37.503-25.735 49.028-9.343 5.634-21.577 9.167-37.477 9.167h-40.97L391.2 249h32.559Zm-24.872 80.393h14.028c21.861 0 34.94-16.302 34.94-34.63 0-13.627-8.153-24.519-22.681-24.519h-13.251l-13.036 59.149Z" fill="#fff"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h600v600H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1 @@
<svg width="600" height="600" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M64.03 318.824c0 3.985-.84 7.658-2.54 11.038-1.68 3.38-4.043 6.232-7.051 8.596s-6.602 4.201-10.763 5.529c-4.16 1.329-8.712 1.973-13.654 1.973-5.918 0-11.212-.801-15.92-2.422-4.707-1.622-9.395-4.298-14.102-8.03l7.774-12.64c3.731 3.126 7.09 5.412 10.118 6.857 3.009 1.446 6.075 2.169 9.22 2.169 3.868 0 6.954-.977 9.317-2.989 2.364-2.013 3.516-4.689 3.516-8.069 0-1.446-.215-2.813-.644-4.083-.43-1.27-1.192-2.559-2.344-3.908-1.133-1.328-2.696-2.774-4.708-4.337-1.973-1.563-4.532-3.497-7.676-5.802-2.403-1.7-4.766-3.439-7.032-5.236a50.634 50.634 0 0 1-6.231-5.802 26.532 26.532 0 0 1-4.512-6.877c-1.133-2.54-1.72-5.432-1.72-8.675 0-3.731.782-7.17 2.345-10.237a23.325 23.325 0 0 1 6.406-7.854c2.696-2.188 5.939-3.868 9.747-5.06 3.79-1.192 7.892-1.817 12.365-1.817 4.707 0 9.24.645 13.634 1.876 4.395 1.27 8.458 3.087 12.208 5.49l-7.051 11.37c-4.825-3.38-9.884-5.06-15.197-5.06-3.242 0-5.938.84-8.048 2.54-2.129 1.699-3.164 3.809-3.164 6.349s.918 4.591 2.793 6.526c1.856 1.934 5.06 4.649 9.63 8.146 4.59 3.38 8.419 6.35 11.447 8.89 3.027 2.539 5.41 4.923 7.168 7.17 1.758 2.246 2.97 4.493 3.633 6.798.665 2.306.997 4.826.997 7.6h.058l-.02-.019Zm105.264-22.233c0 7.111-1.074 13.656-3.282 19.634-2.187 5.979-5.196 11.175-9.083 15.649-3.887 4.474-8.457 7.932-13.79 10.394-5.313 2.462-11.056 3.692-17.248 3.692-2.774 0-5.371-.234-7.794-.722a25.875 25.875 0 0 1-6.895-2.443c-2.168-1.133-4.317-2.618-6.446-4.434-2.129-1.817-4.317-4.045-6.621-6.702V389H74.89V253.356h23.245l.117 13.207c4.238-5.295 8.829-9.202 13.751-11.664 4.962-2.481 10.704-3.692 17.228-3.692 5.919 0 11.33 1.113 16.193 3.341 4.903 2.227 9.122 5.333 12.677 9.319 3.555 3.985 6.31 8.733 8.243 14.301 1.934 5.548 2.891 11.702 2.891 18.442h.039l.02-.019Zm-25.276.938c0-9.906-1.992-17.72-6.016-23.464-4.005-5.744-9.454-8.596-16.389-8.596-7.285 0-13.067 3.047-17.306 9.143-4.239 6.095-6.387 14.34-6.387 24.714 0 10.374 2.109 18.111 6.27 23.913 4.16 5.802 9.942 8.694 17.228 8.694 4.356 0 7.989-1.114 10.841-3.341 2.852-2.247 5.157-5.079 6.915-8.518 1.758-3.438 3.008-7.189 3.731-11.214.722-4.044 1.093-7.815 1.093-11.331h-.019.039Zm35.609 46.243h23.85V211h-23.85v132.772Zm125.735.039v-90.572h-23.85v48.744c0 4.337-.176 7.697-.547 10.061a25.839 25.839 0 0 1-1.817 6.448c-3.379 7.482-9.551 11.233-18.537 11.233-7.012 0-11.934-2.54-14.728-7.619-1.093-1.915-1.855-4.103-2.285-6.525-.43-2.423-.645-5.92-.645-10.531v-51.811h-23.85v51.46c0 3.497.039 6.466.098 8.889.059 2.422.215 4.552.469 6.447.234 1.876.527 3.536.82 4.982a19.198 19.198 0 0 0 1.368 4.161c2.187 5.568 5.703 9.769 10.606 12.601 4.903 2.833 10.9 4.24 18.01 4.24 6.407 0 12.013-1.114 16.857-3.341 4.844-2.247 9.532-5.88 14.142-10.96l.058 12.113h23.85v-.039h-.019v.019Zm102.881-.039v-51.343c0-3.497-.039-6.486-.098-8.947-.058-2.482-.195-4.592-.468-6.428a41.409 41.409 0 0 0-.899-4.787 42.825 42.825 0 0 0-1.27-4.083c-2.187-5.411-5.703-9.592-10.606-12.562-4.903-2.969-10.9-4.435-18.049-4.435-6.407 0-12.013 1.114-16.857 3.361-4.844 2.246-9.532 5.9-14.142 10.96l-.039-12.113H321.75v90.377h24.065v-48.783c0-4.22.156-7.502.469-9.847.312-2.344.879-4.532 1.738-6.603 1.563-3.615 3.965-6.369 7.188-8.225 3.223-1.856 7.052-2.794 11.545-2.794 7.012 0 11.934 2.54 14.728 7.58 1.074 1.915 1.836 4.103 2.265 6.506.43 2.403.645 5.9.645 10.491v51.695h23.85v-.02Zm98.779-5.958-36.351-45.209 30.745-32.919-18.088-7.776-31.995 37.061h-2.539V211h-24.065v132.753h24.065v-48.1l35.98 49.917 22.248-7.776h.02l-.02.02ZM600 305.363v-14.809l-72.331-36.299v16.254l56.06 27.293-56.06 27.645v15.903L600 305.402v-.039Z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -6831,6 +6831,10 @@
"message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning",
"description": "the text, 'SCIM', is an acronym and should not be translated."
},
"scimIntegrationDescription": {
"message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations",
"description": "the text, 'SCIM', is an acronym and should not be translated."
},
"scimEnabledCheckboxDesc": {
"message": "Enable SCIM",
"description": "the text, 'SCIM', is an acronym and should not be translated."
@@ -9025,44 +9029,103 @@
"sdksDesc": {
"message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications."
},
"setUpGithubActions": {
"message": "Set up Github Actions"
"singleSignOn": {
"message": "Single sign-on"
},
"setUpKubernetes": {
"message": "Set up Kubernetes"
"ssoDescStart": {
"message": "Configure",
"description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider."
},
"setUpGitlabCICD": {
"message": "Set up GitLab CI/CD"
"ssoDescEnd": {
"message": "for Bitwarden using the implementation guide for your Identity Provider.",
"description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider."
},
"setUpAnsible": {
"message": "Set up Ansible"
"userProvisioning":{
"message": "User provisioning"
},
"rustSDKRepo": {
"message": "View Rust repository"
"scimIntegration": {
"message": "SCIM"
},
"cSharpSDKRepo": {
"message": "View C# repository"
"scimIntegrationDescStart": {
"message": "Configure ",
"description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider"
},
"cPlusPlusSDKRepo": {
"message": "View C++ repository"
"scimIntegrationDescEnd": {
"message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.",
"description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider"
},
"jsWebAssemblySDKRepo": {
"message": "View JS WebAssembly repository"
"bwdc":{
"message": "Bitwarden Directory Connector"
},
"javaSDKRepo": {
"message": "View Java repository"
"bwdcDesc": {
"message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider."
},
"pythonSDKRepo": {
"message": "View Python repository"
"eventManagement":{
"message": "Event management"
},
"phpSDKRepo": {
"message": "View php repository"
"eventManagementDesc":{
"message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform."
},
"rubySDKRepo": {
"message": "View Ruby repository"
"deviceManagement":{
"message": "Device management"
},
"goSDKRepo": {
"message": "View Go repository"
"deviceManagementDesc":{
"message": "Configure device management for Bitwarden using the implementation guide for your platform."
},
"integrationCardTooltip":{
"message": "Launch $INTEGRATION$ implementation guide.",
"placeholders": {
"integration": {
"content": "$1",
"example": "Google"
}
}
},
"smIntegrationTooltip":{
"message": "Set up $INTEGRATION$.",
"placeholders": {
"integration": {
"content": "$1",
"example": "Google"
}
}
},
"smSdkTooltip":{
"message": "View $SDK$ repository",
"placeholders": {
"sdk": {
"content": "$1",
"example": "Rust"
}
}
},
"integrationCardAriaLabel":{
"message": "open $INTEGRATION$ implementation guide in a new tab.",
"placeholders": {
"integration": {
"content": "$1",
"example": "google"
}
}
},
"smSdkAriaLabel":{
"message": "view $SDK$ repository in a new tab.",
"placeholders": {
"sdk": {
"content": "$1",
"example": "rust"
}
}
},
"smIntegrationCardAriaLabel":{
"message": "set up $INTEGRATION$ implementation guide in a new tab.",
"placeholders": {
"integration": {
"content": "$1",
"example": "google"
}
}
},
"createNewClientToManageAsProvider": {
"message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle."

View File

@@ -1,6 +1,11 @@
<app-header></app-header>
<p bitTypography="body1">{{ "scimDescription" | i18n }}</p>
<p bitTypography="body1">
{{ "scimIntegrationDescription" | i18n }}
<a bitLink target="_blank" href="https://bitwarden.com/help/about-scim/"
><i class="bwi bwi-question-circle"></i
></a>
</p>
<div *ngIf="loading">
<i

View File

@@ -63,17 +63,12 @@ export class MaximumVaultTimeoutPolicyComponent extends BasePolicyComponent {
};
}
buildRequest(policiesEnabledMap: Map<PolicyType, boolean>): Promise<PolicyRequest> {
const singleOrgEnabled = policiesEnabledMap.get(PolicyType.SingleOrg) ?? false;
if (this.enabled.value && !singleOrgEnabled) {
throw new Error(this.i18nService.t("requireSsoPolicyReqError"));
}
const data = this.buildRequestData();
if (data?.minutes == null || data?.minutes <= 0) {
async buildRequest(): Promise<PolicyRequest> {
const request = await super.buildRequest();
if (request.data?.minutes == null || request.data?.minutes <= 0) {
throw new Error(this.i18nService.t("invalidMaximumVaultTimeout"));
}
return super.buildRequest(policiesEnabledMap);
return request;
}
}

View File

@@ -1,15 +0,0 @@
import { Component, Input } from "@angular/core";
import { IntegrationType } from "@bitwarden/common/enums";
import { Integration } from "../models/integration";
@Component({
selector: "sm-integration-grid",
templateUrl: "./integration-grid.component.html",
})
export class IntegrationGridComponent {
@Input() integrations: Integration[];
protected IntegrationType = IntegrationType;
}

View File

@@ -4,7 +4,11 @@
<section class="tw-mb-9">
<p bitTypography="body1">{{ "integrationsDesc" | i18n }}</p>
<sm-integration-grid [integrations]="integrations"></sm-integration-grid>
<app-integration-grid
[integrations]="integrations"
[tooltipI18nKey]="'smIntegrationTooltip'"
[ariaI18nKey]="'smIntegrationCardAriaLabel'"
></app-integration-grid>
</section>
<section class="tw-mb-9">
@@ -12,5 +16,9 @@
{{ "sdks" | i18n }}
</h2>
<p bitTypography="body1">{{ "sdksDesc" | i18n }}</p>
<sm-integration-grid [integrations]="sdks"></sm-integration-grid>
<app-integration-grid
[integrations]="sdks"
[tooltipI18nKey]="'smSdkTooltip'"
[ariaI18nKey]="'smSdkAriaLabel'"
></app-integration-grid>
</section>

View File

@@ -4,14 +4,17 @@ import { By } from "@angular/platform-browser";
import { mock } from "jest-mock-extended";
import { of } from "rxjs";
import { SharedModule } from "@bitwarden/components/src/shared";
import {
IntegrationCardComponent,
IntegrationGridComponent,
} from "@bitwarden/web-vault/app/shared";
import { SYSTEM_THEME_OBSERVABLE } from "../../../../../../libs/angular/src/services/injection-tokens";
import { I18nService } from "../../../../../../libs/common/src/platform/abstractions/i18n.service";
import { ThemeType } from "../../../../../../libs/common/src/platform/enums";
import { ThemeStateService } from "../../../../../../libs/common/src/platform/theming/theme-state.service";
import { I18nPipe } from "../../../../../../libs/components/src/shared/i18n.pipe";
import { IntegrationCardComponent } from "./integration-card/integration-card.component";
import { IntegrationGridComponent } from "./integration-grid/integration-grid.component";
import { IntegrationsComponent } from "./integrations.component";
@Component({
@@ -31,18 +34,12 @@ describe("IntegrationsComponent", () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
IntegrationsComponent,
IntegrationGridComponent,
IntegrationCardComponent,
MockHeaderComponent,
MockNewMenuComponent,
I18nPipe,
],
declarations: [IntegrationsComponent, MockHeaderComponent, MockNewMenuComponent],
imports: [IntegrationGridComponent, IntegrationCardComponent, SharedModule],
providers: [
{
provide: I18nService,
useValue: mock<I18nService>({ t: (key) => key }),
useValue: mock<I18nService>(),
},
{
provide: ThemeStateService,

View File

@@ -1,9 +1,7 @@
import { Component } from "@angular/core";
import { IntegrationType } from "@bitwarden/common/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Integration } from "./models/integration";
import { Integration } from "@bitwarden/web-vault/app/shared";
@Component({
selector: "sm-integrations",
@@ -12,11 +10,10 @@ import { Integration } from "./models/integration";
export class IntegrationsComponent {
private integrationsAndSdks: Integration[] = [];
constructor(i18nService: I18nService) {
constructor() {
this.integrationsAndSdks = [
{
name: "Rust",
linkText: i18nService.t("rustSDKRepo"),
linkURL: "https://github.com/bitwarden/sdk",
image: "../../../../../../../images/secrets-manager/sdks/rust.svg",
imageDarkMode: "../../../../../../../images/secrets-manager/sdks/rust-white.svg",
@@ -24,7 +21,6 @@ export class IntegrationsComponent {
},
{
name: "GitHub Actions",
linkText: i18nService.t("setUpGithubActions"),
linkURL: "https://bitwarden.com/help/github-actions-integration/",
image: "../../../../../../../images/secrets-manager/integrations/github.svg",
imageDarkMode: "../../../../../../../images/secrets-manager/integrations/github-white.svg",
@@ -32,7 +28,6 @@ export class IntegrationsComponent {
},
{
name: "GitLab CI/CD",
linkText: i18nService.t("setUpGitlabCICD"),
linkURL: "https://bitwarden.com/help/gitlab-integration/",
image: "../../../../../../../images/secrets-manager/integrations/gitlab.svg",
imageDarkMode: "../../../../../../../images/secrets-manager/integrations/gitlab-white.svg",
@@ -40,35 +35,30 @@ export class IntegrationsComponent {
},
{
name: "Ansible",
linkText: i18nService.t("setUpAnsible"),
linkURL: "https://bitwarden.com/help/ansible-integration/",
image: "../../../../../../../images/secrets-manager/integrations/ansible.svg",
type: IntegrationType.Integration,
},
{
name: "C#",
linkText: i18nService.t("cSharpSDKRepo"),
linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/csharp",
image: "../../../../../../../images/secrets-manager/sdks/c-sharp.svg",
type: IntegrationType.SDK,
},
{
name: "C++",
linkText: i18nService.t("cPlusPlusSDKRepo"),
linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/cpp",
image: "../../../../../../../images/secrets-manager/sdks/c-plus-plus.png",
type: IntegrationType.SDK,
},
{
name: "Go",
linkText: i18nService.t("goSDKRepo"),
linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/go",
image: "../../../../../../../images/secrets-manager/sdks/go.svg",
type: IntegrationType.SDK,
},
{
name: "Java",
linkText: i18nService.t("javaSDKRepo"),
linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/java",
image: "../../../../../../../images/secrets-manager/sdks/java.svg",
imageDarkMode: "../../../../../../../images/secrets-manager/sdks/java-white.svg",
@@ -76,35 +66,30 @@ export class IntegrationsComponent {
},
{
name: "JS WebAssembly",
linkText: i18nService.t("jsWebAssemblySDKRepo"),
linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/js",
image: "../../../../../../../images/secrets-manager/sdks/wasm.svg",
type: IntegrationType.SDK,
},
{
name: "php",
linkText: i18nService.t("phpSDKRepo"),
linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/php",
image: "../../../../../../../images/secrets-manager/sdks/php.svg",
type: IntegrationType.SDK,
},
{
name: "Python",
linkText: i18nService.t("pythonSDKRepo"),
linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/python",
image: "../../../../../../../images/secrets-manager/sdks/python.svg",
type: IntegrationType.SDK,
},
{
name: "Ruby",
linkText: i18nService.t("rubySDKRepo"),
linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/ruby",
image: "../../../../../../../images/secrets-manager/sdks/ruby.png",
type: IntegrationType.SDK,
},
{
name: "Kubernetes Operator",
linkText: i18nService.t("setUpKubernetes"),
linkURL: "https://bitwarden.com/help/secrets-manager-kubernetes-operator/",
image: "../../../../../../../images/secrets-manager/integrations/kubernetes.svg",
type: IntegrationType.Integration,

View File

@@ -1,15 +1,22 @@
import { NgModule } from "@angular/core";
import {
IntegrationCardComponent,
IntegrationGridComponent,
} from "@bitwarden/web-vault/app/shared";
import { SecretsManagerSharedModule } from "../shared/sm-shared.module";
import { IntegrationCardComponent } from "./integration-card/integration-card.component";
import { IntegrationGridComponent } from "./integration-grid/integration-grid.component";
import { IntegrationsRoutingModule } from "./integrations-routing.module";
import { IntegrationsComponent } from "./integrations.component";
@NgModule({
imports: [SecretsManagerSharedModule, IntegrationsRoutingModule],
declarations: [IntegrationsComponent, IntegrationGridComponent, IntegrationCardComponent],
providers: [],
imports: [
SecretsManagerSharedModule,
IntegrationsRoutingModule,
IntegrationCardComponent,
IntegrationGridComponent,
],
declarations: [IntegrationsComponent],
})
export class IntegrationsModule {}

View File

@@ -1,15 +1,14 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { organizationPermissionsGuard } from "@bitwarden/web-vault/app/admin-console/organizations/guards/org-permissions.guard";
import { RiskInsightsComponent } from "./risk-insights.component";
const routes: Routes = [
{
path: "risk-insights",
canActivate: [canAccessFeature(FeatureFlag.AccessIntelligence)],
canActivate: [organizationPermissionsGuard((org) => org.useRiskInsights)],
component: RiskInsightsComponent,
data: {
titleId: "RiskInsights",

Some files were not shown because too many files have changed in this diff Show More