mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 00:03:56 +00:00
Merge branch 'master' into feature/org-admin-refresh
This commit is contained in:
@@ -68,7 +68,7 @@ export class AvatarComponent implements OnChanges, OnInit {
|
||||
}
|
||||
|
||||
const charObj = this.getCharText(chars);
|
||||
const color = this.stringToColor(upperData);
|
||||
const color = Utils.stringToColor(upperData);
|
||||
const svg = this.getSvg(this.size, color);
|
||||
svg.appendChild(charObj);
|
||||
const html = window.document.createElement("div").appendChild(svg).outerHTML;
|
||||
@@ -77,19 +77,6 @@ export class AvatarComponent implements OnChanges, OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
private stringToColor(str: string): string {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
||||
}
|
||||
let color = "#";
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const value = (hash >> (i * 8)) & 0xff;
|
||||
color += ("00" + value.toString(16)).substr(-2);
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
private getFirstLetters(data: string, count: number): string {
|
||||
const parts = data.split(" ");
|
||||
if (parts.length > 1) {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import Substitute, { Arg } from "@fluffy-spoon/substitute";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { AttachmentData } from "@bitwarden/common/models/data/attachmentData";
|
||||
import { Attachment } from "@bitwarden/common/models/domain/attachment";
|
||||
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
|
||||
import { ContainerService } from "@bitwarden/common/services/container.service";
|
||||
|
||||
@@ -54,30 +56,79 @@ describe("Attachment", () => {
|
||||
expect(attachment.toAttachmentData()).toEqual(data);
|
||||
});
|
||||
|
||||
it("Decrypt", async () => {
|
||||
const attachment = new Attachment();
|
||||
attachment.id = "id";
|
||||
attachment.url = "url";
|
||||
attachment.size = "1100";
|
||||
attachment.sizeName = "1.1 KB";
|
||||
attachment.key = mockEnc("key");
|
||||
attachment.fileName = mockEnc("fileName");
|
||||
describe("decrypt", () => {
|
||||
let cryptoService: MockProxy<CryptoService>;
|
||||
let encryptService: MockProxy<AbstractEncryptService>;
|
||||
|
||||
const cryptoService = Substitute.for<CryptoService>();
|
||||
cryptoService.getOrgKey(null).resolves(null);
|
||||
cryptoService.decryptToBytes(Arg.any(), Arg.any()).resolves(makeStaticByteArray(32));
|
||||
beforeEach(() => {
|
||||
cryptoService = mock<CryptoService>();
|
||||
encryptService = mock<AbstractEncryptService>();
|
||||
|
||||
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
|
||||
(window as any).bitwardenContainerService = new ContainerService(
|
||||
cryptoService,
|
||||
encryptService
|
||||
);
|
||||
});
|
||||
|
||||
const view = await attachment.decrypt(null);
|
||||
it("expected output", async () => {
|
||||
const attachment = new Attachment();
|
||||
attachment.id = "id";
|
||||
attachment.url = "url";
|
||||
attachment.size = "1100";
|
||||
attachment.sizeName = "1.1 KB";
|
||||
attachment.key = mockEnc("key");
|
||||
attachment.fileName = mockEnc("fileName");
|
||||
|
||||
expect(view).toEqual({
|
||||
id: "id",
|
||||
url: "url",
|
||||
size: "1100",
|
||||
sizeName: "1.1 KB",
|
||||
fileName: "fileName",
|
||||
key: expect.any(SymmetricCryptoKey),
|
||||
encryptService.decryptToBytes.mockResolvedValue(makeStaticByteArray(32));
|
||||
|
||||
const view = await attachment.decrypt(null);
|
||||
|
||||
expect(view).toEqual({
|
||||
id: "id",
|
||||
url: "url",
|
||||
size: "1100",
|
||||
sizeName: "1.1 KB",
|
||||
fileName: "fileName",
|
||||
key: expect.any(SymmetricCryptoKey),
|
||||
});
|
||||
});
|
||||
|
||||
describe("decrypts attachment.key", () => {
|
||||
let attachment: Attachment;
|
||||
|
||||
beforeEach(() => {
|
||||
attachment = new Attachment();
|
||||
attachment.key = mock<EncString>();
|
||||
});
|
||||
|
||||
it("uses the provided key without depending on CryptoService", async () => {
|
||||
const providedKey = mock<SymmetricCryptoKey>();
|
||||
|
||||
await attachment.decrypt(null, providedKey);
|
||||
|
||||
expect(cryptoService.getKeyForUserEncryption).not.toHaveBeenCalled();
|
||||
expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, providedKey);
|
||||
});
|
||||
|
||||
it("gets an organization key if required", async () => {
|
||||
const orgKey = mock<SymmetricCryptoKey>();
|
||||
cryptoService.getOrgKey.calledWith("orgId").mockResolvedValue(orgKey);
|
||||
|
||||
await attachment.decrypt("orgId", null);
|
||||
|
||||
expect(cryptoService.getOrgKey).toHaveBeenCalledWith("orgId");
|
||||
expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, orgKey);
|
||||
});
|
||||
|
||||
it("gets the user's decryption key if required", async () => {
|
||||
const userKey = mock<SymmetricCryptoKey>();
|
||||
cryptoService.getKeyForUserEncryption.mockResolvedValue(userKey);
|
||||
|
||||
await attachment.decrypt(null, null);
|
||||
|
||||
expect(cryptoService.getKeyForUserEncryption).toHaveBeenCalled();
|
||||
expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, userKey);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import Substitute, { Arg } from "@fluffy-spoon/substitute";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { EncryptionType } from "@bitwarden/common/enums/encryptionType";
|
||||
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||
@@ -48,10 +50,15 @@ describe("EncString", () => {
|
||||
|
||||
const cryptoService = Substitute.for<CryptoService>();
|
||||
cryptoService.getOrgKey(null).resolves(null);
|
||||
cryptoService.decryptToUtf8(encString, Arg.any()).resolves("decrypted");
|
||||
|
||||
const encryptService = Substitute.for<AbstractEncryptService>();
|
||||
encryptService.decryptToUtf8(encString, Arg.any()).resolves("decrypted");
|
||||
|
||||
beforeEach(() => {
|
||||
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
|
||||
(window as any).bitwardenContainerService = new ContainerService(
|
||||
cryptoService,
|
||||
encryptService
|
||||
);
|
||||
});
|
||||
|
||||
it("decrypts correctly", async () => {
|
||||
@@ -62,7 +69,7 @@ describe("EncString", () => {
|
||||
|
||||
it("result should be cached", async () => {
|
||||
const decrypted = await encString.decrypt(null);
|
||||
cryptoService.received(1).decryptToUtf8(Arg.any(), Arg.any());
|
||||
encryptService.received(1).decryptToUtf8(Arg.any(), Arg.any());
|
||||
|
||||
expect(decrypted).toBe("decrypted");
|
||||
});
|
||||
@@ -148,25 +155,28 @@ describe("EncString", () => {
|
||||
});
|
||||
|
||||
describe("decrypt", () => {
|
||||
it("throws exception when bitwarden container not initialized", async () => {
|
||||
const encString = new EncString(null);
|
||||
let cryptoService: MockProxy<CryptoService>;
|
||||
let encryptService: MockProxy<AbstractEncryptService>;
|
||||
let encString: EncString;
|
||||
|
||||
expect.assertions(1);
|
||||
try {
|
||||
await encString.decrypt(null);
|
||||
} catch (e) {
|
||||
expect(e.message).toEqual("global bitwardenContainerService not initialized.");
|
||||
}
|
||||
beforeEach(() => {
|
||||
cryptoService = mock<CryptoService>();
|
||||
encryptService = mock<AbstractEncryptService>();
|
||||
encString = new EncString(null);
|
||||
|
||||
(window as any).bitwardenContainerService = new ContainerService(
|
||||
cryptoService,
|
||||
encryptService
|
||||
);
|
||||
});
|
||||
|
||||
it("handles value it can't decrypt", async () => {
|
||||
const encString = new EncString(null);
|
||||
encryptService.decryptToUtf8.mockRejectedValue("error");
|
||||
|
||||
const cryptoService = Substitute.for<CryptoService>();
|
||||
cryptoService.getOrgKey(null).resolves(null);
|
||||
cryptoService.decryptToUtf8(encString, Arg.any()).throws("error");
|
||||
|
||||
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
|
||||
(window as any).bitwardenContainerService = new ContainerService(
|
||||
cryptoService,
|
||||
encryptService
|
||||
);
|
||||
|
||||
const decrypted = await encString.decrypt(null);
|
||||
|
||||
@@ -178,18 +188,35 @@ describe("EncString", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("passes along key", async () => {
|
||||
const encString = new EncString(null);
|
||||
const key = Substitute.for<SymmetricCryptoKey>();
|
||||
|
||||
const cryptoService = Substitute.for<CryptoService>();
|
||||
cryptoService.getOrgKey(null).resolves(null);
|
||||
|
||||
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
|
||||
it("uses provided key without depending on CryptoService", async () => {
|
||||
const key = mock<SymmetricCryptoKey>();
|
||||
|
||||
await encString.decrypt(null, key);
|
||||
|
||||
cryptoService.received().decryptToUtf8(encString, key);
|
||||
expect(cryptoService.getKeyForUserEncryption).not.toHaveBeenCalled();
|
||||
expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(encString, key);
|
||||
});
|
||||
|
||||
it("gets an organization key if required", async () => {
|
||||
const orgKey = mock<SymmetricCryptoKey>();
|
||||
|
||||
cryptoService.getOrgKey.calledWith("orgId").mockResolvedValue(orgKey);
|
||||
|
||||
await encString.decrypt("orgId", null);
|
||||
|
||||
expect(cryptoService.getOrgKey).toHaveBeenCalledWith("orgId");
|
||||
expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(encString, orgKey);
|
||||
});
|
||||
|
||||
it("gets the user's decryption key if required", async () => {
|
||||
const userKey = mock<SymmetricCryptoKey>();
|
||||
|
||||
cryptoService.getKeyForUserEncryption.mockResolvedValue(userKey);
|
||||
|
||||
await encString.decrypt(null, null);
|
||||
|
||||
expect(cryptoService.getKeyForUserEncryption).toHaveBeenCalledWith();
|
||||
expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(encString, userKey);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Substitute, { Arg, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
|
||||
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { SendType } from "@bitwarden/common/enums/sendType";
|
||||
import { SendData } from "@bitwarden/common/models/data/sendData";
|
||||
@@ -110,7 +111,9 @@ describe("Send", () => {
|
||||
cryptoService.decryptToBytes(send.key, null).resolves(makeStaticByteArray(32));
|
||||
cryptoService.makeSendKey(Arg.any()).resolves("cryptoKey" as any);
|
||||
|
||||
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
|
||||
const encryptService = Substitute.for<AbstractEncryptService>();
|
||||
|
||||
(window as any).bitwardenContainerService = new ContainerService(cryptoService, encryptService);
|
||||
|
||||
const view = await send.decrypt();
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
import { BehaviorSubject, firstValueFrom } from "rxjs";
|
||||
|
||||
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
|
||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
@@ -15,6 +16,7 @@ describe("Folder Service", () => {
|
||||
let folderService: FolderService;
|
||||
|
||||
let cryptoService: SubstituteOf<CryptoService>;
|
||||
let encryptService: SubstituteOf<AbstractEncryptService>;
|
||||
let i18nService: SubstituteOf<I18nService>;
|
||||
let cipherService: SubstituteOf<CipherService>;
|
||||
let stateService: SubstituteOf<StateService>;
|
||||
@@ -23,6 +25,7 @@ describe("Folder Service", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
cryptoService = Substitute.for();
|
||||
encryptService = Substitute.for();
|
||||
i18nService = Substitute.for();
|
||||
cipherService = Substitute.for();
|
||||
stateService = Substitute.for();
|
||||
@@ -34,7 +37,7 @@ describe("Folder Service", () => {
|
||||
});
|
||||
stateService.activeAccount$.returns(activeAccount);
|
||||
stateService.activeAccountUnlocked$.returns(activeAccountUnlocked);
|
||||
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
|
||||
(window as any).bitwardenContainerService = new ContainerService(cryptoService, encryptService);
|
||||
|
||||
folderService = new FolderService(cryptoService, i18nService, cipherService, stateService);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
import { BehaviorSubject, firstValueFrom } from "rxjs";
|
||||
|
||||
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { ContainerService } from "@bitwarden/common/services/container.service";
|
||||
import { SettingsService } from "@bitwarden/common/services/settings.service";
|
||||
@@ -10,12 +11,14 @@ describe("SettingsService", () => {
|
||||
let settingsService: SettingsService;
|
||||
|
||||
let cryptoService: SubstituteOf<CryptoService>;
|
||||
let encryptService: SubstituteOf<AbstractEncryptService>;
|
||||
let stateService: SubstituteOf<StateService>;
|
||||
let activeAccount: BehaviorSubject<string>;
|
||||
let activeAccountUnlocked: BehaviorSubject<boolean>;
|
||||
|
||||
beforeEach(() => {
|
||||
cryptoService = Substitute.for();
|
||||
encryptService = Substitute.for();
|
||||
stateService = Substitute.for();
|
||||
activeAccount = new BehaviorSubject("123");
|
||||
activeAccountUnlocked = new BehaviorSubject(true);
|
||||
@@ -23,7 +26,7 @@ describe("SettingsService", () => {
|
||||
stateService.getSettings().resolves({ equivalentDomains: [["test"], ["domains"]] });
|
||||
stateService.activeAccount$.returns(activeAccount);
|
||||
stateService.activeAccountUnlocked$.returns(activeAccountUnlocked);
|
||||
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
|
||||
(window as any).bitwardenContainerService = new ContainerService(cryptoService, encryptService);
|
||||
|
||||
settingsService = new SettingsService(stateService);
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import { CollectionData } from "../models/data/collectionData";
|
||||
import { EncryptedOrganizationKeyData } from "../models/data/encryptedOrganizationKeyData";
|
||||
import { EventData } from "../models/data/eventData";
|
||||
import { FolderData } from "../models/data/folderData";
|
||||
import { LocalData } from "../models/data/localData";
|
||||
import { OrganizationData } from "../models/data/organizationData";
|
||||
import { PolicyData } from "../models/data/policyData";
|
||||
import { ProviderData } from "../models/data/providerData";
|
||||
@@ -245,8 +246,11 @@ export abstract class StateService<T extends Account = Account> {
|
||||
setLastActive: (value: number, options?: StorageOptions) => Promise<void>;
|
||||
getLastSync: (options?: StorageOptions) => Promise<string>;
|
||||
setLastSync: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getLocalData: (options?: StorageOptions) => Promise<any>;
|
||||
setLocalData: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getLocalData: (options?: StorageOptions) => Promise<{ [cipherId: string]: LocalData }>;
|
||||
setLocalData: (
|
||||
value: { [cipherId: string]: LocalData },
|
||||
options?: StorageOptions
|
||||
) => Promise<void>;
|
||||
getLocale: (options?: StorageOptions) => Promise<string>;
|
||||
setLocale: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getMainWindowSize: (options?: StorageOptions) => Promise<number>;
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as tldjs from "tldjs";
|
||||
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
|
||||
import { AbstractEncryptService } from "../abstractions/abstractEncrypt.service";
|
||||
import { I18nService } from "../abstractions/i18n.service";
|
||||
|
||||
const nodeURL = typeof window === "undefined" ? require("url") : null;
|
||||
@@ -14,6 +15,7 @@ declare global {
|
||||
|
||||
interface BitwardenContainerService {
|
||||
getCryptoService: () => CryptoService;
|
||||
getEncryptService: () => AbstractEncryptService;
|
||||
}
|
||||
|
||||
export class Utils {
|
||||
@@ -368,6 +370,49 @@ export class Utils {
|
||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* There are a few ways to calculate text color for contrast, this one seems to fit accessibility guidelines best.
|
||||
* https://stackoverflow.com/a/3943023/6869691
|
||||
*
|
||||
* @param {string} bgColor
|
||||
* @param {number} [threshold] see stackoverflow link above
|
||||
* @param {boolean} [svgTextFill]
|
||||
* Indicates if this method is performed on an SVG <text> 'fill' attribute (e.g. <text fill="black"></text>).
|
||||
* This check is necessary because the '!important' tag cannot be used in a 'fill' attribute.
|
||||
*/
|
||||
static pickTextColorBasedOnBgColor(bgColor: string, threshold = 186, svgTextFill = false) {
|
||||
const bgColorHexNums = bgColor.charAt(0) === "#" ? bgColor.substring(1, 7) : bgColor;
|
||||
const r = parseInt(bgColorHexNums.substring(0, 2), 16); // hexToR
|
||||
const g = parseInt(bgColorHexNums.substring(2, 4), 16); // hexToG
|
||||
const b = parseInt(bgColorHexNums.substring(4, 6), 16); // hexToB
|
||||
const blackColor = svgTextFill ? "black" : "black !important";
|
||||
const whiteColor = svgTextFill ? "white" : "white !important";
|
||||
return r * 0.299 + g * 0.587 + b * 0.114 > threshold ? blackColor : whiteColor;
|
||||
}
|
||||
|
||||
static stringToColor(str: string): string {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
||||
}
|
||||
let color = "#";
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const value = (hash >> (i * 8)) & 0xff;
|
||||
color += ("00" + value.toString(16)).substr(-2);
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Will throw an error if the ContainerService has not been attached to the window object
|
||||
*/
|
||||
static getContainerService(): BitwardenContainerService {
|
||||
if (this.global.bitwardenContainerService == null) {
|
||||
throw new Error("global bitwardenContainerService not initialized.");
|
||||
}
|
||||
return this.global.bitwardenContainerService;
|
||||
}
|
||||
|
||||
private static validIpAddress(ipString: string): boolean {
|
||||
const ipRegex =
|
||||
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||
|
||||
4
libs/common/src/models/data/localData.ts
Normal file
4
libs/common/src/models/data/localData.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export type LocalData = {
|
||||
lastUsedDate?: number;
|
||||
lastLaunched?: number;
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
import { CryptoService } from "../../abstractions/crypto.service";
|
||||
import { Utils } from "../../misc/utils";
|
||||
import { AttachmentData } from "../data/attachmentData";
|
||||
import { AttachmentView } from "../view/attachmentView";
|
||||
@@ -47,26 +46,33 @@ export class Attachment extends Domain {
|
||||
);
|
||||
|
||||
if (this.key != null) {
|
||||
let cryptoService: CryptoService;
|
||||
const containerService = Utils.global.bitwardenContainerService;
|
||||
if (containerService) {
|
||||
cryptoService = containerService.getCryptoService();
|
||||
} else {
|
||||
throw new Error("global bitwardenContainerService not initialized.");
|
||||
}
|
||||
|
||||
try {
|
||||
const orgKey = await cryptoService.getOrgKey(orgId);
|
||||
const decValue = await cryptoService.decryptToBytes(this.key, orgKey ?? encKey);
|
||||
view.key = new SymmetricCryptoKey(decValue);
|
||||
} catch (e) {
|
||||
// TODO: error?
|
||||
}
|
||||
view.key = await this.decryptAttachmentKey(orgId, encKey);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private async decryptAttachmentKey(orgId: string, encKey?: SymmetricCryptoKey) {
|
||||
try {
|
||||
if (encKey == null) {
|
||||
encKey = await this.getKeyForDecryption(orgId);
|
||||
}
|
||||
|
||||
const encryptService = Utils.getContainerService().getEncryptService();
|
||||
const decValue = await encryptService.decryptToBytes(this.key, encKey);
|
||||
return new SymmetricCryptoKey(decValue);
|
||||
} catch (e) {
|
||||
// TODO: error?
|
||||
}
|
||||
}
|
||||
|
||||
private async getKeyForDecryption(orgId: string) {
|
||||
const cryptoService = Utils.getContainerService().getCryptoService();
|
||||
return orgId != null
|
||||
? await cryptoService.getOrgKey(orgId)
|
||||
: await cryptoService.getKeyForUserEncryption();
|
||||
}
|
||||
|
||||
toAttachmentData(): AttachmentData {
|
||||
const a = new AttachmentData();
|
||||
a.size = this.size;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { CipherRepromptType } from "../../enums/cipherRepromptType";
|
||||
import { CipherType } from "../../enums/cipherType";
|
||||
import { CipherData } from "../data/cipherData";
|
||||
import { LocalData } from "../data/localData";
|
||||
import { CipherView } from "../view/cipherView";
|
||||
|
||||
import { Attachment } from "./attachment";
|
||||
@@ -26,7 +27,7 @@ export class Cipher extends Domain {
|
||||
edit: boolean;
|
||||
viewPassword: boolean;
|
||||
revisionDate: Date;
|
||||
localData: any;
|
||||
localData: LocalData;
|
||||
login: Login;
|
||||
identity: Identity;
|
||||
card: Card;
|
||||
@@ -38,7 +39,7 @@ export class Cipher extends Domain {
|
||||
deletedDate: Date;
|
||||
reprompt: CipherRepromptType;
|
||||
|
||||
constructor(obj?: CipherData, localData: any = null) {
|
||||
constructor(obj?: CipherData, localData: LocalData = null) {
|
||||
super();
|
||||
if (obj == null) {
|
||||
return;
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Jsonify } from "type-fest";
|
||||
|
||||
import { IEncrypted } from "@bitwarden/common/interfaces/IEncrypted";
|
||||
|
||||
import { CryptoService } from "../../abstractions/crypto.service";
|
||||
import { EncryptionType } from "../../enums/encryptionType";
|
||||
import { Utils } from "../../misc/utils";
|
||||
|
||||
@@ -29,30 +28,6 @@ export class EncString implements IEncrypted {
|
||||
}
|
||||
}
|
||||
|
||||
async decrypt(orgId: string, key: SymmetricCryptoKey = null): Promise<string> {
|
||||
if (this.decryptedValue != null) {
|
||||
return this.decryptedValue;
|
||||
}
|
||||
|
||||
let cryptoService: CryptoService;
|
||||
const containerService = Utils.global.bitwardenContainerService;
|
||||
if (containerService) {
|
||||
cryptoService = containerService.getCryptoService();
|
||||
} else {
|
||||
throw new Error("global bitwardenContainerService not initialized.");
|
||||
}
|
||||
|
||||
try {
|
||||
if (key == null) {
|
||||
key = await cryptoService.getOrgKey(orgId);
|
||||
}
|
||||
this.decryptedValue = await cryptoService.decryptToUtf8(this, key);
|
||||
} catch (e) {
|
||||
this.decryptedValue = "[error: cannot decrypt]";
|
||||
}
|
||||
return this.decryptedValue;
|
||||
}
|
||||
|
||||
get ivBytes(): ArrayBuffer {
|
||||
return this.iv == null ? null : Utils.fromB64ToArray(this.iv).buffer;
|
||||
}
|
||||
@@ -160,4 +135,32 @@ export class EncString implements IEncrypted {
|
||||
encPieces,
|
||||
};
|
||||
}
|
||||
|
||||
async decrypt(orgId: string, key: SymmetricCryptoKey = null): Promise<string> {
|
||||
if (this.decryptedValue != null) {
|
||||
return this.decryptedValue;
|
||||
}
|
||||
|
||||
try {
|
||||
if (key == null) {
|
||||
key = await this.getKeyForDecryption(orgId);
|
||||
}
|
||||
if (key == null) {
|
||||
throw new Error("No key to decrypt EncString with orgId " + orgId);
|
||||
}
|
||||
|
||||
const encryptService = Utils.getContainerService().getEncryptService();
|
||||
this.decryptedValue = await encryptService.decryptToUtf8(this, key);
|
||||
} catch (e) {
|
||||
this.decryptedValue = "[error: cannot decrypt]";
|
||||
}
|
||||
return this.decryptedValue;
|
||||
}
|
||||
|
||||
private async getKeyForDecryption(orgId: string) {
|
||||
const cryptoService = Utils.getContainerService().getCryptoService();
|
||||
return orgId != null
|
||||
? await cryptoService.getOrgKey(orgId)
|
||||
: await cryptoService.getKeyForUserEncryption();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { CryptoService } from "../../abstractions/crypto.service";
|
||||
import { SendType } from "../../enums/sendType";
|
||||
import { Utils } from "../../misc/utils";
|
||||
import { SendData } from "../data/sendData";
|
||||
@@ -71,13 +70,7 @@ export class Send extends Domain {
|
||||
async decrypt(): Promise<SendView> {
|
||||
const model = new SendView(this);
|
||||
|
||||
let cryptoService: CryptoService;
|
||||
const containerService = Utils.global.bitwardenContainerService;
|
||||
if (containerService) {
|
||||
cryptoService = containerService.getCryptoService();
|
||||
} else {
|
||||
throw new Error("global bitwardenContainerService not initialized.");
|
||||
}
|
||||
const cryptoService = Utils.getContainerService().getCryptoService();
|
||||
|
||||
try {
|
||||
model.key = await cryptoService.decryptToBytes(this.key, null);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Jsonify } from "type-fest";
|
||||
import { CipherRepromptType } from "../../enums/cipherRepromptType";
|
||||
import { CipherType } from "../../enums/cipherType";
|
||||
import { LinkedIdType } from "../../enums/linkedIdType";
|
||||
import { LocalData } from "../data/localData";
|
||||
import { Cipher } from "../domain/cipher";
|
||||
|
||||
import { AttachmentView } from "./attachmentView";
|
||||
@@ -25,7 +26,7 @@ export class CipherView implements View {
|
||||
organizationUseTotp = false;
|
||||
edit = false;
|
||||
viewPassword = true;
|
||||
localData: any;
|
||||
localData: LocalData;
|
||||
login = new LoginView();
|
||||
identity = new IdentityView();
|
||||
card = new CardView();
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { AbstractEncryptService } from "../abstractions/abstractEncrypt.service";
|
||||
import { CryptoService } from "../abstractions/crypto.service";
|
||||
|
||||
export class ContainerService {
|
||||
constructor(private cryptoService: CryptoService) {}
|
||||
constructor(
|
||||
private cryptoService: CryptoService,
|
||||
private encryptService: AbstractEncryptService
|
||||
) {}
|
||||
|
||||
attachToGlobal(global: any) {
|
||||
if (!global.bitwardenContainerService) {
|
||||
@@ -9,7 +13,23 @@ export class ContainerService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Will throw if CryptoService was not instantiated and provided to the ContainerService constructor
|
||||
*/
|
||||
getCryptoService(): CryptoService {
|
||||
if (this.cryptoService == null) {
|
||||
throw new Error("ContainerService.cryptoService not initialized.");
|
||||
}
|
||||
return this.cryptoService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Will throw if EncryptService was not instantiated and provided to the ContainerService constructor
|
||||
*/
|
||||
getEncryptService(): AbstractEncryptService {
|
||||
if (this.encryptService == null) {
|
||||
throw new Error("ContainerService.encryptService not initialized.");
|
||||
}
|
||||
return this.encryptService;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ export class I18nService implements I18nServiceAbstraction {
|
||||
collator: Intl.Collator;
|
||||
localeNames = new Map<string, string>([
|
||||
["af", "Afrikaans"],
|
||||
["ar", "العربية الفصحى"],
|
||||
["az", "Azərbaycanca"],
|
||||
["be", "Беларуская"],
|
||||
["bg", "български"],
|
||||
|
||||
@@ -16,6 +16,7 @@ import { CollectionData } from "../models/data/collectionData";
|
||||
import { EncryptedOrganizationKeyData } from "../models/data/encryptedOrganizationKeyData";
|
||||
import { EventData } from "../models/data/eventData";
|
||||
import { FolderData } from "../models/data/folderData";
|
||||
import { LocalData } from "../models/data/localData";
|
||||
import { OrganizationData } from "../models/data/organizationData";
|
||||
import { PolicyData } from "../models/data/policyData";
|
||||
import { ProviderData } from "../models/data/providerData";
|
||||
@@ -1747,12 +1748,16 @@ export class StateService<
|
||||
);
|
||||
}
|
||||
|
||||
async getLocalData(options?: StorageOptions): Promise<any> {
|
||||
async getLocalData(options?: StorageOptions): Promise<{ [cipherId: string]: LocalData }> {
|
||||
return (
|
||||
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()))
|
||||
)?.data?.localData;
|
||||
}
|
||||
async setLocalData(value: string, options?: StorageOptions): Promise<void> {
|
||||
|
||||
async setLocalData(
|
||||
value: { [cipherId: string]: LocalData },
|
||||
options?: StorageOptions
|
||||
): Promise<void> {
|
||||
const account = await this.getAccount(
|
||||
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "@bitwarden/components",
|
||||
"version": "0.0.0",
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
|
||||
127
libs/components/src/avatar/avatar.component.ts
Normal file
127
libs/components/src/avatar/avatar.component.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { Component, Input, OnChanges } from "@angular/core";
|
||||
import { DomSanitizer, SafeResourceUrl } from "@angular/platform-browser";
|
||||
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
|
||||
type SizeTypes = "large" | "default" | "small";
|
||||
|
||||
const SizeClasses: Record<SizeTypes, string[]> = {
|
||||
large: ["tw-h-16", "tw-w-16"],
|
||||
default: ["tw-h-12", "tw-w-12"],
|
||||
small: ["tw-h-7", "tw-w-7"],
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: "bit-avatar",
|
||||
template: `<img *ngIf="src" [src]="src" title="{{ text }}" [ngClass]="classList" />`,
|
||||
})
|
||||
export class AvatarComponent implements OnChanges {
|
||||
@Input() border = false;
|
||||
@Input() color: string;
|
||||
@Input() id: number;
|
||||
@Input() text: string;
|
||||
@Input() size: SizeTypes = "default";
|
||||
|
||||
private svgCharCount = 2;
|
||||
private svgFontSize = 20;
|
||||
private svgFontWeight = 300;
|
||||
private svgSize = 48;
|
||||
src: SafeResourceUrl;
|
||||
|
||||
constructor(public sanitizer: DomSanitizer) {}
|
||||
|
||||
ngOnChanges() {
|
||||
this.generate();
|
||||
}
|
||||
|
||||
get classList() {
|
||||
return ["tw-rounded-full"]
|
||||
.concat(SizeClasses[this.size] ?? [])
|
||||
.concat(this.border ? ["tw-border", "tw-border-solid", "tw-border-secondary-500"] : []);
|
||||
}
|
||||
|
||||
private generate() {
|
||||
let chars: string = null;
|
||||
const upperCaseText = this.text.toUpperCase();
|
||||
|
||||
chars = this.getFirstLetters(upperCaseText, this.svgCharCount);
|
||||
|
||||
if (chars == null) {
|
||||
chars = this.unicodeSafeSubstring(upperCaseText, this.svgCharCount);
|
||||
}
|
||||
|
||||
// If the chars contain an emoji, only show it.
|
||||
if (chars.match(Utils.regexpEmojiPresentation)) {
|
||||
chars = chars.match(Utils.regexpEmojiPresentation)[0];
|
||||
}
|
||||
|
||||
let svg: HTMLElement;
|
||||
let hexColor = this.color;
|
||||
|
||||
if (this.color != null) {
|
||||
svg = this.createSvgElement(this.svgSize, hexColor);
|
||||
} else if (this.id != null) {
|
||||
hexColor = Utils.stringToColor(this.id.toString());
|
||||
svg = this.createSvgElement(this.svgSize, hexColor);
|
||||
} else {
|
||||
hexColor = Utils.stringToColor(upperCaseText);
|
||||
svg = this.createSvgElement(this.svgSize, hexColor);
|
||||
}
|
||||
|
||||
const charObj = this.createTextElement(chars, hexColor);
|
||||
svg.appendChild(charObj);
|
||||
const html = window.document.createElement("div").appendChild(svg).outerHTML;
|
||||
const svgHtml = window.btoa(unescape(encodeURIComponent(html)));
|
||||
this.src = this.sanitizer.bypassSecurityTrustResourceUrl(
|
||||
"data:image/svg+xml;base64," + svgHtml
|
||||
);
|
||||
}
|
||||
|
||||
private getFirstLetters(data: string, count: number): string {
|
||||
const parts = data.split(" ");
|
||||
if (parts.length > 1) {
|
||||
let text = "";
|
||||
for (let i = 0; i < count; i++) {
|
||||
text += this.unicodeSafeSubstring(parts[i], 1);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private createSvgElement(size: number, color: string): HTMLElement {
|
||||
const svgTag = window.document.createElement("svg");
|
||||
svgTag.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
||||
svgTag.setAttribute("pointer-events", "none");
|
||||
svgTag.setAttribute("width", size.toString());
|
||||
svgTag.setAttribute("height", size.toString());
|
||||
svgTag.style.backgroundColor = color;
|
||||
svgTag.style.width = size + "px";
|
||||
svgTag.style.height = size + "px";
|
||||
return svgTag;
|
||||
}
|
||||
|
||||
private createTextElement(character: string, color: string): HTMLElement {
|
||||
const textTag = window.document.createElement("text");
|
||||
textTag.setAttribute("text-anchor", "middle");
|
||||
textTag.setAttribute("y", "50%");
|
||||
textTag.setAttribute("x", "50%");
|
||||
textTag.setAttribute("dy", "0.35em");
|
||||
textTag.setAttribute("pointer-events", "auto");
|
||||
textTag.setAttribute("fill", Utils.pickTextColorBasedOnBgColor(color, 135, true));
|
||||
textTag.setAttribute(
|
||||
"font-family",
|
||||
'"Open Sans","Helvetica Neue",Helvetica,Arial,' +
|
||||
'sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"'
|
||||
);
|
||||
textTag.textContent = character;
|
||||
textTag.style.fontWeight = this.svgFontWeight.toString();
|
||||
textTag.style.fontSize = this.svgFontSize + "px";
|
||||
return textTag;
|
||||
}
|
||||
|
||||
private unicodeSafeSubstring(str: string, count: number) {
|
||||
const characters = str.match(/./gu);
|
||||
return characters != null ? characters.slice(0, count).join("") : "";
|
||||
}
|
||||
}
|
||||
11
libs/components/src/avatar/avatar.module.ts
Normal file
11
libs/components/src/avatar/avatar.module.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { AvatarComponent } from "./avatar.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
exports: [AvatarComponent],
|
||||
declarations: [AvatarComponent],
|
||||
})
|
||||
export class AvatarModule {}
|
||||
60
libs/components/src/avatar/avatar.stories.ts
Normal file
60
libs/components/src/avatar/avatar.stories.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Meta, Story } from "@storybook/angular";
|
||||
|
||||
import { AvatarComponent } from "./avatar.component";
|
||||
|
||||
export default {
|
||||
title: "Component Library/Avatar",
|
||||
component: AvatarComponent,
|
||||
args: {
|
||||
text: "Walt Walterson",
|
||||
size: "default",
|
||||
},
|
||||
parameters: {
|
||||
design: {
|
||||
type: "figma",
|
||||
url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A16994",
|
||||
},
|
||||
},
|
||||
} as Meta;
|
||||
|
||||
const Template: Story<AvatarComponent> = (args: AvatarComponent) => ({
|
||||
props: args,
|
||||
});
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
color: "#175ddc",
|
||||
};
|
||||
|
||||
export const Large = Template.bind({});
|
||||
Large.args = {
|
||||
...Default.args,
|
||||
size: "large",
|
||||
};
|
||||
|
||||
export const Small = Template.bind({});
|
||||
Small.args = {
|
||||
...Default.args,
|
||||
size: "small",
|
||||
};
|
||||
|
||||
export const LightBackground = Template.bind({});
|
||||
LightBackground.args = {
|
||||
color: "#d2ffcf",
|
||||
};
|
||||
|
||||
export const Border = Template.bind({});
|
||||
Border.args = {
|
||||
...Default.args,
|
||||
border: true,
|
||||
};
|
||||
|
||||
export const ColorByID = Template.bind({});
|
||||
ColorByID.args = {
|
||||
id: 236478,
|
||||
};
|
||||
|
||||
export const ColorByText = Template.bind({});
|
||||
ColorByText.args = {
|
||||
text: "Jason Doe",
|
||||
};
|
||||
2
libs/components/src/avatar/index.ts
Normal file
2
libs/components/src/avatar/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./avatar.module";
|
||||
export * from "./avatar.component";
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, HostBinding, Input } from "@angular/core";
|
||||
import { DomSanitizer } from "@angular/platform-browser";
|
||||
|
||||
import { Icon, IconSvg } from "./icons";
|
||||
import { Icon, isIcon } from "./icon";
|
||||
|
||||
@Component({
|
||||
selector: "bit-icon",
|
||||
@@ -14,11 +14,11 @@ export class BitIconComponent {
|
||||
|
||||
@HostBinding("innerHtml")
|
||||
protected get innerHtml() {
|
||||
const svg = IconSvg[this.icon];
|
||||
if (svg == null) {
|
||||
return "Unknown icon";
|
||||
if (!isIcon(this.icon)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return this.domSanitizer.bypassSecurityTrustHtml(IconSvg[this.icon]);
|
||||
const svg = this.icon.svg;
|
||||
return this.domSanitizer.bypassSecurityTrustHtml(svg);
|
||||
}
|
||||
}
|
||||
|
||||
39
libs/components/src/icon/icon.components.spec.ts
Normal file
39
libs/components/src/icon/icon.components.spec.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
|
||||
import { Icon, svgIcon } from "./icon";
|
||||
import { BitIconComponent } from "./icon.component";
|
||||
|
||||
describe("IconComponent", () => {
|
||||
let component: BitIconComponent;
|
||||
let fixture: ComponentFixture<BitIconComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [BitIconComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(BitIconComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it("should have empty innerHtml when input is not an Icon", () => {
|
||||
const fakeIcon = { svg: "harmful user input" } as Icon;
|
||||
|
||||
component.icon = fakeIcon;
|
||||
fixture.detectChanges();
|
||||
|
||||
const el = fixture.nativeElement as HTMLElement;
|
||||
expect(el.innerHTML).toBe("");
|
||||
});
|
||||
|
||||
it("should contain icon when input is a safe Icon", () => {
|
||||
const icon = svgIcon`<svg><text x="0" y="15">safe icon</text></svg>`;
|
||||
|
||||
component.icon = icon;
|
||||
fixture.detectChanges();
|
||||
|
||||
const el = fixture.nativeElement as HTMLElement;
|
||||
expect(el.innerHTML).toBe(`<svg><text x="0" y="15">safe icon</text></svg>`);
|
||||
});
|
||||
});
|
||||
38
libs/components/src/icon/icon.spec.ts
Normal file
38
libs/components/src/icon/icon.spec.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import * as IconExports from "./icon";
|
||||
import { DynamicContentNotAllowedError, isIcon, svgIcon } from "./icon";
|
||||
|
||||
describe("Icon", () => {
|
||||
it("exports should not expose Icon class", () => {
|
||||
expect(Object.keys(IconExports)).not.toContain("Icon");
|
||||
});
|
||||
|
||||
describe("isIcon", () => {
|
||||
it("should return true when input is icon", () => {
|
||||
const result = isIcon(svgIcon`icon`);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when input is not an icon", () => {
|
||||
const result = isIcon({ svg: "not an icon" });
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("template literal", () => {
|
||||
it("should throw when attempting to create dynamic icons", () => {
|
||||
const dynamic = "some user input";
|
||||
|
||||
const f = () => svgIcon`static and ${dynamic}`;
|
||||
|
||||
expect(f).toThrow(DynamicContentNotAllowedError);
|
||||
});
|
||||
|
||||
it("should return svg content when supplying icon with svg string", () => {
|
||||
const icon = svgIcon`safe static content`;
|
||||
|
||||
expect(icon.svg).toBe("safe static content");
|
||||
});
|
||||
});
|
||||
});
|
||||
25
libs/components/src/icon/icon.ts
Normal file
25
libs/components/src/icon/icon.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
class Icon {
|
||||
constructor(readonly svg: string) {}
|
||||
}
|
||||
|
||||
// We only export the type to prohibit the creation of Icons without using
|
||||
// the `svgIcon` template literal tag.
|
||||
export type { Icon };
|
||||
|
||||
export function isIcon(icon: unknown): icon is Icon {
|
||||
return icon instanceof Icon;
|
||||
}
|
||||
|
||||
export class DynamicContentNotAllowedError extends Error {
|
||||
constructor() {
|
||||
super("Dynamic content in icons is not allowed due to risk of user-injected XSS.");
|
||||
}
|
||||
}
|
||||
|
||||
export function svgIcon(strings: TemplateStringsArray, ...values: unknown[]): Icon {
|
||||
if (values.length > 0) {
|
||||
throw new DynamicContentNotAllowedError();
|
||||
}
|
||||
|
||||
return new Icon(strings[0]);
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
export const IconSvg = {
|
||||
reportExposedPasswords: `
|
||||
<svg width="101" height="77" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.374 50.192a26.42 26.42 0 0 0 9.111 1.608c14.34 0 25.965-11.372 25.965-25.4 0-.337-.007-.673-.02-1.008h25.299v34.85H32.374v-10.05Z" fill="currentColor" />
|
||||
<path d="M15.805 26.4c0 14.028 11.625 25.4 25.965 25.4s25.964-11.372 25.964-25.4C67.734 12.372 56.11 1 41.77 1 27.43 1 15.805 12.372 15.805 26.4Z" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M27.914 47.849a1 1 0 0 0-2 0h2Zm68.288-26.792a2.12 2.12 0 0 1 2.14 2.11h2c0-2.253-1.83-4.11-4.14-4.11v2Zm2.14 2.11v40.552h2V23.167h-2Zm0 40.552c0 1.172-.958 2.11-2.14 2.11v2c2.25 0 4.14-1.798 4.14-4.11h-2Zm-2.14 2.11H30.054v2h66.148v-2Zm-66.148 0a2.12 2.12 0 0 1-2.14-2.11h-2a4.12 4.12 0 0 0 4.14 4.11v-2Zm-2.14-2.11V47.85h-2v15.87h2Zm39.254-42.662h29.034v-2H67.168v2Z" fill="#fff" />
|
||||
<path d="M67.203 25.56h25.64v34.85H32.487V50.011" stroke="#fff" stroke-width="2" stroke-linejoin="round" />
|
||||
<path d="M47.343 76h31.571" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M57.557 66.83V76M67.771 66.83V76" stroke="#fff" stroke-width="2" stroke-linejoin="round" />
|
||||
<path d="m20.995 42.873-3.972 3.972-14.61 14.61a3.413 3.413 0 0 0 0 4.826v0a3.413 3.413 0 0 0 4.827 0l14.61-14.61 3.972-3.972" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M86.037 32.488H71.845M86.037 37.81H76.28M71.845 37.81h-6.652M86.037 43.132h-6.209M74.95 43.132H61.2M86.037 48.454H71.845M66.967 48.454h-7.54M86.037 53.776H66.08M61.201 53.776h-11.53M44.793 53.776h-7.096" stroke="#fff" stroke-width="2" stroke-linecap="round" />
|
||||
<rect width="40.801" height="9.757" rx="4" transform="matrix(-1 0 0 1 61.201 14.748)" stroke="#fff" stroke-width="2" />
|
||||
<path d="M16.852 33.375h28.375a4 4 0 0 1 4 4v1.757a4 4 0 0 1-4 4H22.174M66.523 33.375h-3.539a4 4 0 0 0-4 4v3.761c0 1.102.894 1.996 1.996 1.996v0" stroke="#fff" stroke-width="2" stroke-linecap="round" />
|
||||
</svg>
|
||||
`,
|
||||
reportReusedPasswords: `
|
||||
<svg width="102" height="102" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M57.983 15.06a35.664 35.664 0 0 1 14.531 6.27c16.164 11.78 19.585 34.613 7.643 51a37.227 37.227 0 0 1-6.81 7.138m-32.842 6.697a35.708 35.708 0 0 1-11.239-5.495c-16.163-11.78-19.585-34.613-7.642-51a37.55 37.55 0 0 1 3.295-3.929" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M93.909 64.598H7.72c-.708 0-1.275-.662-1.275-1.49V40.273c0-.828.567-1.49 1.275-1.49H93.91c.708 0 1.275.663 1.275 1.49v22.837c.047.827-.567 1.49-1.275 1.49Z" fill="currentColor" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M21.532 52.186v-5.965M21.532 52.187l5.748-1.844M21.532 52.186l3.524 4.881M21.531 52.186l-3.47 4.881M21.532 52.187l-5.694-1.844M40.944 52.186v-5.965M40.944 52.187l5.694-1.844M40.944 52.187l3.525 4.88M40.944 52.187l-3.525 4.88M40.944 52.187l-5.694-1.844M54.849 57.337h11.294M74.21 57.337h11.295M41.75 83l.71 4.75-4.75.71M58.664 18.66 56 14.665 59.996 12" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
`,
|
||||
reportWeakPasswords: `
|
||||
<svg width="78" height="78" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M66.493 64.415V77H9.979V64.324M9.979 44.065V32.106h56.514v12.148" stroke="#fff" stroke-width="2" stroke-linejoin="round" />
|
||||
<path d="M75.44 64.852H2.085c-.603 0-1.085-.555-1.085-1.25V44.448c0-.694.482-1.25 1.085-1.25H75.44c.603 0 1.085.556 1.085 1.25v19.156c.04.694-.482 1.25-1.085 1.25Z" fill="currentColor" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M13.84 54.56v-5.077M13.84 54.56l4.893-1.57M13.84 54.56l3 4.153M13.84 54.56l-2.954 4.153M13.84 54.56l-4.846-1.57M30.363 54.56v-5.077M30.363 54.56l4.846-1.57M30.363 54.56l3 4.153M30.363 54.56l-3 4.153M30.363 54.56l-4.846-1.57M42.197 59.042h9.506M58.57 59.042h9.507" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M20.863 31.364c-.274-5.285 0-15.817 1.093-18.863 1.276-3.554 6.233-10.826 15.856-11.482 4.83-.273 15.2 2.296 18.043 14.763" stroke="#fff" stroke-width="2" />
|
||||
</svg>
|
||||
`,
|
||||
reportUnsecuredWebsites: `
|
||||
<svg width="113" height="76" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.71 12.983h110.362v55.11a6 6 0 0 1-6 6H7.711a6 6 0 0 1-6-6v-55.11Z" fill="currentColor" />
|
||||
<rect x="1" y="1.073" width="110.5" height="73.454" rx="9" stroke="#fff" stroke-width="2" />
|
||||
<path d="M89.48 8.048V7.47M96.363 8.048V7.47M103.246 8.048V7.47" stroke="#fff" stroke-width="4" stroke-linecap="round" />
|
||||
<path d="M0 12.983h111.217" stroke="#fff" stroke-width="2" />
|
||||
<path d="m93.236 44.384-18.42-11.026 2.93 21.266 5.582-5.237 4.27 6.46 2.98-1.971-4.26-6.446 6.918-3.046Z" fill="#175DDC" stroke="#fff" stroke-width="2" stroke-linejoin="round" />
|
||||
<rect width="96.673" height="6.886" rx="3.443" transform="matrix(-1 0 0 1 104.373 18.721)" stroke="#fff" />
|
||||
</svg>
|
||||
`,
|
||||
reportInactiveTwoFactor: `
|
||||
<svg width="42" height="75" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="currentColor" stroke="#fff" stroke-width="2" d="M1 13.121h39.595v48.758H1z" />
|
||||
<rect x="1" y="1" width="39.595" height="73" rx="8" stroke="#fff" stroke-width="2" />
|
||||
<path stroke="#fff" stroke-width="2" stroke-linecap="round" d="M12.344 8.091h16.907M18.907 67.424h3.025M31.503 32.515c-2.047-4.337-6.717-7.061-11.73-6.414a11.356 11.356 0 0 0-9.125 7.126M10.816 42.016c2.047 4.337 6.718 7.062 11.73 6.414 4.346-.562 7.8-3.51 9.213-7.358" />
|
||||
<path d="m33.584 29.293-1.295 4.625-4.625-1.295M8.523 44.725l1.441-4.581 4.582 1.441" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
`,
|
||||
reportBreach: `
|
||||
<svg width="58" height="75" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M39.569 74H13.007a7 7 0 0 1-7-7V31.077a7 7 0 0 1 7-7h19.101a7 7 0 0 1 4.988 2.088l7.46 7.576a7 7 0 0 1 2.013 4.912V67a7 7 0 0 1-7 7Z" fill="#175DDC" stroke="#fff" stroke-width="2" />
|
||||
<path d="M44.576 69.055H18.015a7 7 0 0 1-7-7V26.132a7 7 0 0 1 7-7h19.1a7 7 0 0 1 4.988 2.088l7.46 7.576a7 7 0 0 1 2.013 4.911v28.348a7 7 0 0 1-7 7Z" fill="#175DDC" stroke="#fff" stroke-width="2" />
|
||||
<path d="M50 63.698H23.439a7 7 0 0 1-7-7V20.775a7 7 0 0 1 7-7h19.1a7 7 0 0 1 4.988 2.088l7.46 7.575A7 7 0 0 1 57 28.35v28.348a7 7 0 0 1-7 7Z" fill="#175DDC" stroke="#fff" stroke-width="2" />
|
||||
<path d="M44.648 13.599v3.95a8 8 0 0 0 8 8h4.518" stroke="#fff" stroke-width="2" />
|
||||
<path stroke="#fff" stroke-width="2" stroke-linecap="round" d="M23.533 37.736H49.49M23.533 46.802H49.49M23.533 42.269H49.49M23.533 55.456H49.49M23.533 50.923H49.49" />
|
||||
<path d="M1 16.483C1 7.944 8.013 1 16.69 1c8.678 0 15.691 6.944 15.691 15.483 0 8.54-7.013 15.484-15.69 15.484C8.012 31.967 1 25.023 1 16.484Z" fill="#518FFF" stroke="#fff" stroke-width="2" />
|
||||
<path d="m16.562 7.979.1 11.538" stroke="#fff" stroke-width="2" stroke-linecap="round" />
|
||||
<ellipse rx="1.252" ry="1.236" transform="rotate(-.479 2802.219 -1964.476) skewX(.012)" fill="#fff" />
|
||||
</svg>
|
||||
`,
|
||||
};
|
||||
|
||||
export type Icon = keyof typeof IconSvg;
|
||||
4
libs/components/src/icon/icons/index.ts
Normal file
4
libs/components/src/icon/icons/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// Put generic icons in this folder and export them here.
|
||||
// Note: Icons need to be in separate files for tree-shaking to work properly
|
||||
|
||||
export {}; // <- remove when adding icons in here
|
||||
@@ -1 +1,3 @@
|
||||
export * from "./icon.module";
|
||||
export * from "./icon";
|
||||
export * as Icons from "./icons";
|
||||
|
||||
Reference in New Issue
Block a user