mirror of
https://github.com/bitwarden/directory-connector
synced 2025-12-05 23:53:21 +00:00
[AC-1743] pt. 2.5 ⮕ Fix the unit tests for good (#398)
* Remove uneeded redirects to a tsconfig.spec.json * Got the tests running again * Delete webCryptoFunctionService * Delete unused utils tests * Add a test workflow
This commit is contained in:
54
.github/workflows/test.yml
vendored
Normal file
54
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
name: Run tests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- "rc"
|
||||
- "hotfix-rc-*"
|
||||
pull_request: {}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Run tests
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
|
||||
- name: Get Node Version
|
||||
id: retrieve-node-version
|
||||
run: |
|
||||
NODE_NVMRC=$(cat .nvmrc)
|
||||
NODE_VERSION=${NODE_NVMRC/v/''}
|
||||
echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
node-version: ${{ steps.retrieve-node-version.outputs.node_version }}
|
||||
|
||||
- name: Print environment
|
||||
run: |
|
||||
node --version
|
||||
npm --version
|
||||
|
||||
- name: Install Node dependencies
|
||||
run: npm ci
|
||||
|
||||
# We use isolatedModules: true which disables typechecking in tests
|
||||
# Tests in apps/ are typechecked when their app is built, so we just do it here for libs/
|
||||
# See https://bitwarden.atlassian.net/browse/EC-497
|
||||
- name: Run typechecking
|
||||
run: npm run test:types --coverage
|
||||
|
||||
- name: Run tests
|
||||
run: npm run test
|
||||
@@ -1,27 +1,42 @@
|
||||
const { pathsToModuleNameMapper } = require("ts-jest");
|
||||
|
||||
const { compilerOptions } = require("./tsconfig");
|
||||
|
||||
/** @type {import('jest').Config} */
|
||||
const tsPreset = require("ts-jest/jest-preset");
|
||||
const angularPreset = require("jest-preset-angular/jest-preset");
|
||||
const { defaultTransformerOptions } = require("jest-preset-angular/presets");
|
||||
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
module.exports = {
|
||||
reporters: ["default", "jest-junit"],
|
||||
// ...tsPreset,
|
||||
// ...angularPreset,
|
||||
preset: "jest-preset-angular",
|
||||
|
||||
collectCoverage: true,
|
||||
coverageReporters: ["html", "lcov"],
|
||||
coverageDirectory: "coverage",
|
||||
testEnvironment: "jsdom",
|
||||
testMatch: ["**/+(*.)+(spec).+(ts)"],
|
||||
|
||||
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
|
||||
prefix: "<rootDir>/",
|
||||
}),
|
||||
projects: [
|
||||
"<rootDir>/jslib/angular/jest.config.js",
|
||||
"<rootDir>/jslib/common/jest.config.js",
|
||||
"<rootDir>/jslib/electron/jest.config.js",
|
||||
"<rootDir>/jslib/node/jest.config.js",
|
||||
],
|
||||
roots: ["<rootDir>"],
|
||||
modulePaths: [compilerOptions.baseUrl],
|
||||
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: "<rootDir>/" }),
|
||||
setupFilesAfterEnv: ["<rootDir>/test.setup.ts"],
|
||||
|
||||
// Workaround for a memory leak that crashes tests in CI:
|
||||
// https://github.com/facebook/jest/issues/9430#issuecomment-1149882002
|
||||
// Also anecdotally improves performance when run locally
|
||||
maxWorkers: 3,
|
||||
|
||||
transform: {
|
||||
"^.+\\.tsx?$": [
|
||||
"jest-preset-angular",
|
||||
// 'ts-jest',
|
||||
{
|
||||
...defaultTransformerOptions,
|
||||
tsconfig: "./tsconfig.json",
|
||||
// Further workaround for memory leak, recommended here:
|
||||
// https://github.com/kulshekhar/ts-jest/issues/1967#issuecomment-697494014
|
||||
// Makes tests run faster and reduces size/rate of leak, but loses typechecking on test code
|
||||
// See https://bitwarden.atlassian.net/browse/EC-497 for more info
|
||||
isolatedModules: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
const { pathsToModuleNameMapper } = require("ts-jest");
|
||||
|
||||
const { compilerOptions } = require("../shared/tsconfig.libs");
|
||||
|
||||
const sharedConfig = require("../shared/jest.config.angular");
|
||||
|
||||
/** @type {import('jest').Config} */
|
||||
module.exports = {
|
||||
...sharedConfig,
|
||||
displayName: "libs/angular tests",
|
||||
preset: "jest-preset-angular",
|
||||
setupFilesAfterEnv: ["<rootDir>/test.setup.ts"],
|
||||
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
|
||||
prefix: "<rootDir>/",
|
||||
}),
|
||||
};
|
||||
@@ -72,7 +72,6 @@ import { TwoFactorService } from "@/jslib/common/src/services/twoFactor.service"
|
||||
import { UserVerificationService } from "@/jslib/common/src/services/userVerification.service";
|
||||
import { UsernameGenerationService } from "@/jslib/common/src/services/usernameGeneration.service";
|
||||
import { VaultTimeoutService } from "@/jslib/common/src/services/vaultTimeout.service";
|
||||
import { WebCryptoFunctionService } from "@/jslib/common/src/services/webCryptoFunction.service";
|
||||
|
||||
import { AuthGuardService } from "./auth-guard.service";
|
||||
import { BroadcasterService } from "./broadcaster.service";
|
||||
@@ -420,11 +419,6 @@ import { ValidationService } from "./validation.service";
|
||||
StateServiceAbstraction,
|
||||
],
|
||||
},
|
||||
{
|
||||
provide: CryptoFunctionServiceAbstraction,
|
||||
useClass: WebCryptoFunctionService,
|
||||
deps: ["WINDOW"],
|
||||
},
|
||||
{
|
||||
provide: EventServiceAbstraction,
|
||||
useClass: EventService,
|
||||
|
||||
15
jslib/common/custom-matchers.d.ts
vendored
15
jslib/common/custom-matchers.d.ts
vendored
@@ -1,15 +0,0 @@
|
||||
import type { CustomMatchers } from "./test.setup";
|
||||
|
||||
// This declares the types for our custom matchers so that they're recognised by Typescript
|
||||
// This file must also be included in the TS compilation (via the tsconfig.json "include" property) to be recognised by
|
||||
// vscode
|
||||
|
||||
/* eslint-disable */
|
||||
declare global {
|
||||
namespace jest {
|
||||
interface Expect extends CustomMatchers {}
|
||||
interface Matchers<R> extends CustomMatchers<R> {}
|
||||
interface InverseAsymmetricMatchers extends CustomMatchers {}
|
||||
}
|
||||
}
|
||||
/* eslint-enable */
|
||||
@@ -1,17 +0,0 @@
|
||||
const { pathsToModuleNameMapper } = require("ts-jest");
|
||||
|
||||
const { compilerOptions } = require("../shared/tsconfig.libs");
|
||||
|
||||
const sharedConfig = require("../shared/jest.config.ts");
|
||||
|
||||
/** @type {import('jest').Config} */
|
||||
module.exports = {
|
||||
...sharedConfig,
|
||||
displayName: "libs/common tests",
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "jsdom",
|
||||
setupFilesAfterEnv: ["<rootDir>/test.setup.ts"],
|
||||
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
|
||||
prefix: "<rootDir>/",
|
||||
}),
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./matchers";
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./to-equal-buffer";
|
||||
@@ -1,25 +0,0 @@
|
||||
import { makeStaticByteArray } from "../utils";
|
||||
|
||||
describe("toEqualBuffer custom matcher", () => {
|
||||
it("matches identical ArrayBuffers", () => {
|
||||
const array = makeStaticByteArray(10);
|
||||
expect(array.buffer).toEqualBuffer(array.buffer);
|
||||
});
|
||||
|
||||
it("matches an identical ArrayBuffer and Uint8Array", () => {
|
||||
const array = makeStaticByteArray(10);
|
||||
expect(array.buffer).toEqualBuffer(array);
|
||||
});
|
||||
|
||||
it("doesn't match different ArrayBuffers", () => {
|
||||
const array1 = makeStaticByteArray(10);
|
||||
const array2 = makeStaticByteArray(10, 11);
|
||||
expect(array1.buffer).not.toEqualBuffer(array2.buffer);
|
||||
});
|
||||
|
||||
it("doesn't match a different ArrayBuffer and Uint8Array", () => {
|
||||
const array1 = makeStaticByteArray(10);
|
||||
const array2 = makeStaticByteArray(10, 11);
|
||||
expect(array1.buffer).not.toEqualBuffer(array2);
|
||||
});
|
||||
});
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* The inbuilt toEqual() matcher will always return TRUE when provided with 2 ArrayBuffers.
|
||||
* This is because an ArrayBuffer must be wrapped in a new Uint8Array to be accessible.
|
||||
* This custom matcher will automatically instantiate a new Uint8Array on the received value
|
||||
* (and optionally, the expected value) and then call toEqual() on the resulting Uint8Arrays.
|
||||
*/
|
||||
export const toEqualBuffer: jest.CustomMatcher = function (
|
||||
received: ArrayBuffer | Uint8Array,
|
||||
expected: ArrayBuffer | Uint8Array
|
||||
) {
|
||||
received = new Uint8Array(received);
|
||||
expected = new Uint8Array(expected);
|
||||
|
||||
if (this.equals(received, expected)) {
|
||||
return {
|
||||
message: () => `expected
|
||||
${received}
|
||||
not to match
|
||||
${expected}`,
|
||||
pass: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
message: () => `expected
|
||||
${received}
|
||||
to match
|
||||
${expected}`,
|
||||
pass: false,
|
||||
};
|
||||
};
|
||||
@@ -1,73 +0,0 @@
|
||||
import { Utils } from "@/jslib/common/src/misc/utils";
|
||||
|
||||
describe("Utils Service", () => {
|
||||
describe("getDomain", () => {
|
||||
it("should fail for invalid urls", () => {
|
||||
expect(Utils.getDomain(null)).toBeNull();
|
||||
expect(Utils.getDomain(undefined)).toBeNull();
|
||||
expect(Utils.getDomain(" ")).toBeNull();
|
||||
expect(Utils.getDomain('https://bit!:"_&ward.com')).toBeNull();
|
||||
expect(Utils.getDomain("bitwarden")).toBeNull();
|
||||
});
|
||||
|
||||
it("should fail for data urls", () => {
|
||||
expect(Utils.getDomain("data:image/jpeg;base64,AAA")).toBeNull();
|
||||
});
|
||||
|
||||
it("should handle urls without protocol", () => {
|
||||
expect(Utils.getDomain("bitwarden.com")).toBe("bitwarden.com");
|
||||
expect(Utils.getDomain("wrong://bitwarden.com")).toBe("bitwarden.com");
|
||||
});
|
||||
|
||||
it("should handle valid urls", () => {
|
||||
expect(Utils.getDomain("https://bitwarden")).toBe("bitwarden");
|
||||
expect(Utils.getDomain("https://bitwarden.com")).toBe("bitwarden.com");
|
||||
expect(Utils.getDomain("http://bitwarden.com")).toBe("bitwarden.com");
|
||||
expect(Utils.getDomain("http://vault.bitwarden.com")).toBe("bitwarden.com");
|
||||
expect(
|
||||
Utils.getDomain("https://user:password@bitwarden.com:8080/password/sites?and&query#hash")
|
||||
).toBe("bitwarden.com");
|
||||
expect(Utils.getDomain("https://bitwarden.unknown")).toBe("bitwarden.unknown");
|
||||
});
|
||||
|
||||
it("should support localhost and IP", () => {
|
||||
expect(Utils.getDomain("https://localhost")).toBe("localhost");
|
||||
expect(Utils.getDomain("https://192.168.1.1")).toBe("192.168.1.1");
|
||||
});
|
||||
|
||||
it("should reject invalid hostnames", () => {
|
||||
expect(Utils.getDomain("https://mywebsite.com$.mywebsite.com")).toBeNull();
|
||||
expect(Utils.getDomain("https://mywebsite.com!.mywebsite.com")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getHostname", () => {
|
||||
it("should fail for invalid urls", () => {
|
||||
expect(Utils.getHostname(null)).toBeNull();
|
||||
expect(Utils.getHostname(undefined)).toBeNull();
|
||||
expect(Utils.getHostname(" ")).toBeNull();
|
||||
expect(Utils.getHostname('https://bit!:"_&ward.com')).toBeNull();
|
||||
expect(Utils.getHostname("bitwarden")).toBeNull();
|
||||
});
|
||||
|
||||
it("should handle valid urls", () => {
|
||||
expect(Utils.getHostname("bitwarden.com")).toBe("bitwarden.com");
|
||||
expect(Utils.getHostname("https://bitwarden.com")).toBe("bitwarden.com");
|
||||
expect(Utils.getHostname("http://bitwarden.com")).toBe("bitwarden.com");
|
||||
expect(Utils.getHostname("http://vault.bitwarden.com")).toBe("vault.bitwarden.com");
|
||||
});
|
||||
|
||||
it("should support localhost and IP", () => {
|
||||
expect(Utils.getHostname("https://localhost")).toBe("localhost");
|
||||
expect(Utils.getHostname("https://192.168.1.1")).toBe("192.168.1.1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("newGuid", () => {
|
||||
it("should create a valid guid", () => {
|
||||
const validGuid =
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||
expect(Utils.newGuid()).toMatch(validGuid);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,558 +0,0 @@
|
||||
import { Substitute } from "@fluffy-spoon/substitute";
|
||||
|
||||
import { PlatformUtilsService } from "@/jslib/common/src/abstractions/platformUtils.service";
|
||||
import { Utils } from "@/jslib/common/src/misc/utils";
|
||||
import { SymmetricCryptoKey } from "@/jslib/common/src/models/domain/symmetricCryptoKey";
|
||||
import { WebCryptoFunctionService } from "@/jslib/common/src/services/webCryptoFunction.service";
|
||||
|
||||
const RsaPublicKey =
|
||||
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0Vawl/toXzkEvB82FEtqHP" +
|
||||
"4xlU2ab/v0crqIfXfIoWF/XXdHGIdrZeilnRXPPJT1B9dTsasttEZNnua/0Rek/cjNDHtzT52irfoZYS7X6HNIfOi54Q+egP" +
|
||||
"RQ1H7iNHVZz3K8Db9GCSKPeC8MbW6gVCzb15esCe1gGzg6wkMuWYDFYPoh/oBqcIqrGah7firqB1nDedzEjw32heP2DAffVN" +
|
||||
"084iTDjiWrJNUxBJ2pDD5Z9dT3MzQ2s09ew1yMWK2z37rT3YerC7OgEDmo3WYo3xL3qYJznu3EO2nmrYjiRa40wKSjxsTlUc" +
|
||||
"xDF+F0uMW8oR9EMUHgepdepfAtLsSAQIDAQAB";
|
||||
const RsaPrivateKey =
|
||||
"MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXRVrCX+2hfOQS8Hz" +
|
||||
"YUS2oc/jGVTZpv+/Ryuoh9d8ihYX9dd0cYh2tl6KWdFc88lPUH11Oxqy20Rk2e5r/RF6T9yM0Me3NPnaKt+hlhLtfoc0h86L" +
|
||||
"nhD56A9FDUfuI0dVnPcrwNv0YJIo94LwxtbqBULNvXl6wJ7WAbODrCQy5ZgMVg+iH+gGpwiqsZqHt+KuoHWcN53MSPDfaF4/" +
|
||||
"YMB99U3TziJMOOJask1TEEnakMPln11PczNDazT17DXIxYrbPfutPdh6sLs6AQOajdZijfEvepgnOe7cQ7aeatiOJFrjTApK" +
|
||||
"PGxOVRzEMX4XS4xbyhH0QxQeB6l16l8C0uxIBAgMBAAECggEASaWfeVDA3cVzOPFSpvJm20OTE+R6uGOU+7vh36TX/POq92q" +
|
||||
"Buwbd0h0oMD32FxsXywd2IxtBDUSiFM9699qufTVuM0Q3tZw6lHDTOVG08+tPdr8qSbMtw7PGFxN79fHLBxejjO4IrM9lapj" +
|
||||
"WpxEF+11x7r+wM+0xRZQ8sNFYG46aPfIaty4BGbL0I2DQ2y8I57iBCAy69eht59NLMm27fRWGJIWCuBIjlpfzET1j2HLXUIh" +
|
||||
"5bTBNzqaN039WH49HczGE3mQKVEJZc/efk3HaVd0a1Sjzyn0QY+N1jtZN3jTRbuDWA1AknkX1LX/0tUhuS3/7C3ejHxjw4Dk" +
|
||||
"1ZLo5/QKBgQDIWvqFn0+IKRSu6Ua2hDsufIHHUNLelbfLUMmFthxabcUn4zlvIscJO00Tq/ezopSRRvbGiqnxjv/mYxucvOU" +
|
||||
"BeZtlus0Q9RTACBtw9TGoNTmQbEunJ2FOSlqbQxkBBAjgGEppRPt30iGj/VjAhCATq2MYOa/X4dVR51BqQAFIEwKBgQDBSIf" +
|
||||
"TFKC/hDk6FKZlgwvupWYJyU9RkyfstPErZFmzoKhPkQ3YORo2oeAYmVUbS9I2iIYpYpYQJHX8jMuCbCz4ONxTCuSIXYQYUcU" +
|
||||
"q4PglCKp31xBAE6TN8SvhfME9/MvuDssnQinAHuF0GDAhF646T3LLS1not6Vszv7brwSoGwKBgQC88v/8cGfi80ssQZeMnVv" +
|
||||
"q1UTXIeQcQnoY5lGHJl3K8mbS3TnXE6c9j417Fdz+rj8KWzBzwWXQB5pSPflWcdZO886Xu/mVGmy9RWgLuVFhXwCwsVEPjNX" +
|
||||
"5ramRb0/vY0yzenUCninBsIxFSbIfrPtLUYCc4hpxr+sr2Mg/y6jpvQKBgBezMRRs3xkcuXepuI2R+BCXL1/b02IJTUf1F+1" +
|
||||
"eLLGd7YV0H+J3fgNc7gGWK51hOrF9JBZHBGeOUPlaukmPwiPdtQZpu4QNE3l37VlIpKTF30E6mb+BqR+nht3rUjarnMXgAoE" +
|
||||
"Z18y6/KIjpSMpqC92Nnk/EBM9EYe6Cf4eA9ApAoGAeqEUg46UTlJySkBKURGpIs3v1kkf5I0X8DnOhwb+HPxNaiEdmO7ckm8" +
|
||||
"+tPVgppLcG0+tMdLjigFQiDUQk2y3WjyxP5ZvXu7U96jaJRI8PFMoE06WeVYcdIzrID2HvqH+w0UQJFrLJ/0Mn4stFAEzXKZ" +
|
||||
"BokBGnjFnTnKcs7nv/O8=";
|
||||
|
||||
const Sha1Mac = "4d4c223f95dc577b665ec4ccbcb680b80a397038";
|
||||
const Sha256Mac = "6be3caa84922e12aaaaa2f16c40d44433bb081ef323db584eb616333ab4e874f";
|
||||
const Sha512Mac =
|
||||
"21910e341fa12106ca35758a2285374509326c9fbe0bd64e7b99c898f841dc948c58ce66d3504d8883c" +
|
||||
"5ea7817a0b7c5d4d9b00364ccd214669131fc17fe4aca";
|
||||
|
||||
describe("WebCrypto Function Service", () => {
|
||||
describe("pbkdf2", () => {
|
||||
const regular256Key = "pj9prw/OHPleXI6bRdmlaD+saJS4awrMiQsQiDjeu2I=";
|
||||
const utf8256Key = "yqvoFXgMRmHR3QPYr5pyR4uVuoHkltv9aHUP63p8n7I=";
|
||||
const unicode256Key = "ZdeOata6xoRpB4DLp8zHhXz5kLmkWtX5pd+TdRH8w8w=";
|
||||
|
||||
const regular512Key =
|
||||
"liTi/Ke8LPU1Qv+Vl7NGEVt/XMbsBVJ2kQxtVG/Z1/JFHFKQW3ZkI81qVlwTiCpb+cFXzs+57" +
|
||||
"eyhhx5wfKo5Cg==";
|
||||
const utf8512Key =
|
||||
"df0KdvIBeCzD/kyXptwQohaqUa4e7IyFUyhFQjXCANu5T+scq55hCcE4dG4T/MhAk2exw8j7ixRN" +
|
||||
"zXANiVZpnw==";
|
||||
const unicode512Key =
|
||||
"FE+AnUJaxv8jh+zUDtZz4mjjcYk0/PZDZm+SLJe3XtxtnpdqqpblX6JjuMZt/dYYNMOrb2+mD" +
|
||||
"L3FiQDTROh1lg==";
|
||||
|
||||
testPbkdf2("sha256", regular256Key, utf8256Key, unicode256Key);
|
||||
testPbkdf2("sha512", regular512Key, utf8512Key, unicode512Key);
|
||||
});
|
||||
|
||||
describe("hkdf", () => {
|
||||
const regular256Key = "qBUmEYtwTwwGPuw/z6bs/qYXXYNUlocFlyAuuANI8Pw=";
|
||||
const utf8256Key = "6DfJwW1R3txgiZKkIFTvVAb7qVlG7lKcmJGJoxR2GBU=";
|
||||
const unicode256Key = "gejGI82xthA+nKtKmIh82kjw+ttHr+ODsUoGdu5sf0A=";
|
||||
|
||||
const regular512Key = "xe5cIG6ZfwGmb1FvsOedM0XKOm21myZkjL/eDeKIqqM=";
|
||||
const utf8512Key = "XQMVBnxVEhlvjSFDQc77j5GDE9aorvbS0vKnjhRg0LY=";
|
||||
const unicode512Key = "148GImrTbrjaGAe/iWEpclINM8Ehhko+9lB14+52lqc=";
|
||||
|
||||
testHkdf("sha256", regular256Key, utf8256Key, unicode256Key);
|
||||
testHkdf("sha512", regular512Key, utf8512Key, unicode512Key);
|
||||
});
|
||||
|
||||
describe("hkdfExpand", () => {
|
||||
const prk16Byte = "criAmKtfzxanbgea5/kelQ==";
|
||||
const prk32Byte = "F5h4KdYQnIVH4rKH0P9CZb1GrR4n16/sJrS0PsQEn0Y=";
|
||||
const prk64Byte =
|
||||
"ssBK0mRG17VHdtsgt8yo4v25CRNpauH+0r2fwY/E9rLyaFBAOMbIeTry+" +
|
||||
"gUJ28p8y+hFh3EI9pcrEWaNvFYonQ==";
|
||||
|
||||
testHkdfExpand("sha256", prk32Byte, 32, "BnIqJlfnHm0e/2iB/15cbHyR19ARPIcWRp4oNS22CD8=");
|
||||
testHkdfExpand(
|
||||
"sha256",
|
||||
prk32Byte,
|
||||
64,
|
||||
"BnIqJlfnHm0e/2iB/15cbHyR19ARPIcWRp4oNS22CD9BV+" +
|
||||
"/queOZenPNkDhmlVyL2WZ3OSU5+7ISNF5NhNfvZA=="
|
||||
);
|
||||
testHkdfExpand("sha512", prk64Byte, 32, "uLWbMWodSBms5uGJ5WTRTesyW+MD7nlpCZvagvIRXlk=");
|
||||
testHkdfExpand(
|
||||
"sha512",
|
||||
prk64Byte,
|
||||
64,
|
||||
"uLWbMWodSBms5uGJ5WTRTesyW+MD7nlpCZvagvIRXlkY5Pv0sB+" +
|
||||
"MqvaopmkC6sD/j89zDwTV9Ib2fpucUydO8w=="
|
||||
);
|
||||
|
||||
it("should fail with prk too small", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const f = cryptoFunctionService.hkdfExpand(
|
||||
Utils.fromB64ToArray(prk16Byte),
|
||||
"info",
|
||||
32,
|
||||
"sha256"
|
||||
);
|
||||
await expect(f).rejects.toEqual(new Error("prk is too small."));
|
||||
});
|
||||
|
||||
it("should fail with outputByteSize is too large", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const f = cryptoFunctionService.hkdfExpand(
|
||||
Utils.fromB64ToArray(prk32Byte),
|
||||
"info",
|
||||
8161,
|
||||
"sha256"
|
||||
);
|
||||
await expect(f).rejects.toEqual(new Error("outputByteSize is too large."));
|
||||
});
|
||||
});
|
||||
|
||||
describe("hash", () => {
|
||||
const regular1Hash = "2a241604fb921fad12bf877282457268e1dccb70";
|
||||
const utf81Hash = "85672798dc5831e96d6c48655d3d39365a9c88b6";
|
||||
const unicode1Hash = "39c975935054a3efc805a9709b60763a823a6ad4";
|
||||
|
||||
const regular256Hash = "2b8e96031d352a8655d733d7a930b5ffbea69dc25cf65c7bca7dd946278908b2";
|
||||
const utf8256Hash = "25fe8440f5b01ed113b0a0e38e721b126d2f3f77a67518c4a04fcde4e33eeb9d";
|
||||
const unicode256Hash = "adc1c0c2afd6e92cefdf703f9b6eb2c38e0d6d1a040c83f8505c561fea58852e";
|
||||
|
||||
const regular512Hash =
|
||||
"c15cf11d43bde333647e3f559ec4193bb2edeaa0e8b902772f514cdf3f785a3f49a6e02a4b87b3" +
|
||||
"b47523271ad45b7e0aebb5cdcc1bc54815d256eb5dcb80da9d";
|
||||
const utf8512Hash =
|
||||
"035c31a877a291af09ed2d3a1a293e69c3e079ea2cecc00211f35e6bce10474ca3ad6e30b59e26118" +
|
||||
"37463f20969c5bc95282965a051a88f8cdf2e166549fcdd";
|
||||
const unicode512Hash =
|
||||
"2b16a5561af8ad6fe414cc103fc8036492e1fc6d9aabe1b655497054f760fe0e34c5d100ac773d" +
|
||||
"9f3030438284f22dbfa20cb2e9b019f2c98dfe38ce1ef41bae";
|
||||
|
||||
const regularMd5 = "5eceffa53a5fd58c44134211e2c5f522";
|
||||
const utf8Md5 = "3abc9433c09551b939c80aa0aa3174e1";
|
||||
const unicodeMd5 = "85ae134072c8d81257933f7045ba17ca";
|
||||
|
||||
testHash("sha1", regular1Hash, utf81Hash, unicode1Hash);
|
||||
testHash("sha256", regular256Hash, utf8256Hash, unicode256Hash);
|
||||
testHash("sha512", regular512Hash, utf8512Hash, unicode512Hash);
|
||||
testHash("md5", regularMd5, utf8Md5, unicodeMd5);
|
||||
});
|
||||
|
||||
describe("hmac", () => {
|
||||
testHmac("sha1", Sha1Mac);
|
||||
testHmac("sha256", Sha256Mac);
|
||||
testHmac("sha512", Sha512Mac);
|
||||
});
|
||||
|
||||
describe("compare", () => {
|
||||
it("should successfully compare two of the same values", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const a = new Uint8Array(2);
|
||||
a[0] = 1;
|
||||
a[1] = 2;
|
||||
const equal = await cryptoFunctionService.compare(a.buffer, a.buffer);
|
||||
expect(equal).toBe(true);
|
||||
});
|
||||
|
||||
it("should successfully compare two different values of the same length", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const a = new Uint8Array(2);
|
||||
a[0] = 1;
|
||||
a[1] = 2;
|
||||
const b = new Uint8Array(2);
|
||||
b[0] = 3;
|
||||
b[1] = 4;
|
||||
const equal = await cryptoFunctionService.compare(a.buffer, b.buffer);
|
||||
expect(equal).toBe(false);
|
||||
});
|
||||
|
||||
it("should successfully compare two different values of different lengths", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const a = new Uint8Array(2);
|
||||
a[0] = 1;
|
||||
a[1] = 2;
|
||||
const b = new Uint8Array(2);
|
||||
b[0] = 3;
|
||||
const equal = await cryptoFunctionService.compare(a.buffer, b.buffer);
|
||||
expect(equal).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hmacFast", () => {
|
||||
testHmacFast("sha1", Sha1Mac);
|
||||
testHmacFast("sha256", Sha256Mac);
|
||||
testHmacFast("sha512", Sha512Mac);
|
||||
});
|
||||
|
||||
describe("compareFast", () => {
|
||||
it("should successfully compare two of the same values", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const a = new Uint8Array(2);
|
||||
a[0] = 1;
|
||||
a[1] = 2;
|
||||
const aByteString = Utils.fromBufferToByteString(a.buffer);
|
||||
const equal = await cryptoFunctionService.compareFast(aByteString, aByteString);
|
||||
expect(equal).toBe(true);
|
||||
});
|
||||
|
||||
it("should successfully compare two different values of the same length", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const a = new Uint8Array(2);
|
||||
a[0] = 1;
|
||||
a[1] = 2;
|
||||
const aByteString = Utils.fromBufferToByteString(a.buffer);
|
||||
const b = new Uint8Array(2);
|
||||
b[0] = 3;
|
||||
b[1] = 4;
|
||||
const bByteString = Utils.fromBufferToByteString(b.buffer);
|
||||
const equal = await cryptoFunctionService.compareFast(aByteString, bByteString);
|
||||
expect(equal).toBe(false);
|
||||
});
|
||||
|
||||
it("should successfully compare two different values of different lengths", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const a = new Uint8Array(2);
|
||||
a[0] = 1;
|
||||
a[1] = 2;
|
||||
const aByteString = Utils.fromBufferToByteString(a.buffer);
|
||||
const b = new Uint8Array(2);
|
||||
b[0] = 3;
|
||||
const bByteString = Utils.fromBufferToByteString(b.buffer);
|
||||
const equal = await cryptoFunctionService.compareFast(aByteString, bByteString);
|
||||
expect(equal).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("aesEncrypt", () => {
|
||||
it("should successfully encrypt data", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const iv = makeStaticByteArray(16);
|
||||
const key = makeStaticByteArray(32);
|
||||
const data = Utils.fromUtf8ToArray("EncryptMe!");
|
||||
const encValue = await cryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer);
|
||||
expect(Utils.fromBufferToB64(encValue)).toBe("ByUF8vhyX4ddU9gcooznwA==");
|
||||
});
|
||||
|
||||
it("should successfully encrypt and then decrypt data fast", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const iv = makeStaticByteArray(16);
|
||||
const key = makeStaticByteArray(32);
|
||||
const value = "EncryptMe!";
|
||||
const data = Utils.fromUtf8ToArray(value);
|
||||
const encValue = await cryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer);
|
||||
const encData = Utils.fromBufferToB64(encValue);
|
||||
const b64Iv = Utils.fromBufferToB64(iv.buffer);
|
||||
const symKey = new SymmetricCryptoKey(key.buffer);
|
||||
const params = cryptoFunctionService.aesDecryptFastParameters(encData, b64Iv, null, symKey);
|
||||
const decValue = await cryptoFunctionService.aesDecryptFast(params);
|
||||
expect(decValue).toBe(value);
|
||||
});
|
||||
|
||||
it("should successfully encrypt and then decrypt data", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const iv = makeStaticByteArray(16);
|
||||
const key = makeStaticByteArray(32);
|
||||
const value = "EncryptMe!";
|
||||
const data = Utils.fromUtf8ToArray(value);
|
||||
const encValue = await cryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer);
|
||||
const decValue = await cryptoFunctionService.aesDecrypt(encValue, iv.buffer, key.buffer);
|
||||
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
|
||||
});
|
||||
});
|
||||
|
||||
describe("aesDecryptFast", () => {
|
||||
it("should successfully decrypt data", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const iv = Utils.fromBufferToB64(makeStaticByteArray(16).buffer);
|
||||
const symKey = new SymmetricCryptoKey(makeStaticByteArray(32).buffer);
|
||||
const data = "ByUF8vhyX4ddU9gcooznwA==";
|
||||
const params = cryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey);
|
||||
const decValue = await cryptoFunctionService.aesDecryptFast(params);
|
||||
expect(decValue).toBe("EncryptMe!");
|
||||
});
|
||||
});
|
||||
|
||||
describe("aesDecrypt", () => {
|
||||
it("should successfully decrypt data", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const iv = makeStaticByteArray(16);
|
||||
const key = makeStaticByteArray(32);
|
||||
const data = Utils.fromB64ToArray("ByUF8vhyX4ddU9gcooznwA==");
|
||||
const decValue = await cryptoFunctionService.aesDecrypt(data.buffer, iv.buffer, key.buffer);
|
||||
expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!");
|
||||
});
|
||||
});
|
||||
|
||||
describe("rsaEncrypt", () => {
|
||||
it("should successfully encrypt and then decrypt data", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const pubKey = Utils.fromB64ToArray(RsaPublicKey);
|
||||
const privKey = Utils.fromB64ToArray(RsaPrivateKey);
|
||||
const value = "EncryptMe!";
|
||||
const data = Utils.fromUtf8ToArray(value);
|
||||
const encValue = await cryptoFunctionService.rsaEncrypt(data.buffer, pubKey.buffer, "sha1");
|
||||
const decValue = await cryptoFunctionService.rsaDecrypt(encValue, privKey.buffer, "sha1");
|
||||
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
|
||||
});
|
||||
});
|
||||
|
||||
describe("rsaDecrypt", () => {
|
||||
it("should successfully decrypt data", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const privKey = Utils.fromB64ToArray(RsaPrivateKey);
|
||||
const data = Utils.fromB64ToArray(
|
||||
"A1/p8BQzN9UrbdYxUY2Va5+kPLyfZXF9JsZrjeEXcaclsnHurdxVAJcnbEqYMP3UXV" +
|
||||
"4YAS/mpf+Rxe6/X0WS1boQdA0MAHSgx95hIlAraZYpiMLLiJRKeo2u8YivCdTM9V5vuAEJwf9Tof/qFsFci3sApdbATkorCT" +
|
||||
"zFOIEPF2S1zgperEP23M01mr4dWVdYN18B32YF67xdJHMbFhp5dkQwv9CmscoWq7OE5HIfOb+JAh7BEZb+CmKhM3yWJvoR/D" +
|
||||
"/5jcercUtK2o+XrzNrL4UQ7yLZcFz6Bfwb/j6ICYvqd/YJwXNE6dwlL57OfwJyCdw2rRYf0/qI00t9u8Iitw=="
|
||||
);
|
||||
const decValue = await cryptoFunctionService.rsaDecrypt(data.buffer, privKey.buffer, "sha1");
|
||||
expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!");
|
||||
});
|
||||
});
|
||||
|
||||
describe("rsaExtractPublicKey", () => {
|
||||
it("should successfully extract key", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const privKey = Utils.fromB64ToArray(RsaPrivateKey);
|
||||
const publicKey = await cryptoFunctionService.rsaExtractPublicKey(privKey.buffer);
|
||||
expect(Utils.fromBufferToB64(publicKey)).toBe(RsaPublicKey);
|
||||
});
|
||||
});
|
||||
|
||||
describe("rsaGenerateKeyPair", () => {
|
||||
testRsaGenerateKeyPair(1024);
|
||||
testRsaGenerateKeyPair(2048);
|
||||
|
||||
// Generating 4096 bit keys can be slow. Commenting it out to save CI.
|
||||
// testRsaGenerateKeyPair(4096);
|
||||
});
|
||||
|
||||
describe("randomBytes", () => {
|
||||
it("should make a value of the correct length", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const randomData = await cryptoFunctionService.randomBytes(16);
|
||||
expect(randomData.byteLength).toBe(16);
|
||||
});
|
||||
|
||||
it("should not make the same value twice", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const randomData = await cryptoFunctionService.randomBytes(16);
|
||||
const randomData2 = await cryptoFunctionService.randomBytes(16);
|
||||
expect(
|
||||
randomData.byteLength === randomData2.byteLength && randomData !== randomData2
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function testPbkdf2(
|
||||
algorithm: "sha256" | "sha512",
|
||||
regularKey: string,
|
||||
utf8Key: string,
|
||||
unicodeKey: string
|
||||
) {
|
||||
const regularEmail = "user@example.com";
|
||||
const utf8Email = "üser@example.com";
|
||||
|
||||
const regularPassword = "password";
|
||||
const utf8Password = "pǻssword";
|
||||
const unicodePassword = "😀password🙏";
|
||||
|
||||
it("should create valid " + algorithm + " key from regular input", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const key = await cryptoFunctionService.pbkdf2(regularPassword, regularEmail, algorithm, 5000);
|
||||
expect(Utils.fromBufferToB64(key)).toBe(regularKey);
|
||||
});
|
||||
|
||||
it("should create valid " + algorithm + " key from utf8 input", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const key = await cryptoFunctionService.pbkdf2(utf8Password, utf8Email, algorithm, 5000);
|
||||
expect(Utils.fromBufferToB64(key)).toBe(utf8Key);
|
||||
});
|
||||
|
||||
it("should create valid " + algorithm + " key from unicode input", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const key = await cryptoFunctionService.pbkdf2(unicodePassword, regularEmail, algorithm, 5000);
|
||||
expect(Utils.fromBufferToB64(key)).toBe(unicodeKey);
|
||||
});
|
||||
|
||||
it("should create valid " + algorithm + " key from array buffer input", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const key = await cryptoFunctionService.pbkdf2(
|
||||
Utils.fromUtf8ToArray(regularPassword).buffer,
|
||||
Utils.fromUtf8ToArray(regularEmail).buffer,
|
||||
algorithm,
|
||||
5000
|
||||
);
|
||||
expect(Utils.fromBufferToB64(key)).toBe(regularKey);
|
||||
});
|
||||
}
|
||||
|
||||
function testHkdf(
|
||||
algorithm: "sha256" | "sha512",
|
||||
regularKey: string,
|
||||
utf8Key: string,
|
||||
unicodeKey: string
|
||||
) {
|
||||
const ikm = Utils.fromB64ToArray("criAmKtfzxanbgea5/kelQ==");
|
||||
|
||||
const regularSalt = "salt";
|
||||
const utf8Salt = "üser_salt";
|
||||
const unicodeSalt = "😀salt🙏";
|
||||
|
||||
const regularInfo = "info";
|
||||
const utf8Info = "üser_info";
|
||||
const unicodeInfo = "😀info🙏";
|
||||
|
||||
it("should create valid " + algorithm + " key from regular input", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const key = await cryptoFunctionService.hkdf(ikm, regularSalt, regularInfo, 32, algorithm);
|
||||
expect(Utils.fromBufferToB64(key)).toBe(regularKey);
|
||||
});
|
||||
|
||||
it("should create valid " + algorithm + " key from utf8 input", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const key = await cryptoFunctionService.hkdf(ikm, utf8Salt, utf8Info, 32, algorithm);
|
||||
expect(Utils.fromBufferToB64(key)).toBe(utf8Key);
|
||||
});
|
||||
|
||||
it("should create valid " + algorithm + " key from unicode input", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const key = await cryptoFunctionService.hkdf(ikm, unicodeSalt, unicodeInfo, 32, algorithm);
|
||||
expect(Utils.fromBufferToB64(key)).toBe(unicodeKey);
|
||||
});
|
||||
|
||||
it("should create valid " + algorithm + " key from array buffer input", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const key = await cryptoFunctionService.hkdf(
|
||||
ikm,
|
||||
Utils.fromUtf8ToArray(regularSalt).buffer,
|
||||
Utils.fromUtf8ToArray(regularInfo).buffer,
|
||||
32,
|
||||
algorithm
|
||||
);
|
||||
expect(Utils.fromBufferToB64(key)).toBe(regularKey);
|
||||
});
|
||||
}
|
||||
|
||||
function testHkdfExpand(
|
||||
algorithm: "sha256" | "sha512",
|
||||
b64prk: string,
|
||||
outputByteSize: number,
|
||||
b64ExpectedOkm: string
|
||||
) {
|
||||
const info = "info";
|
||||
|
||||
it("should create valid " + algorithm + " " + outputByteSize + " byte okm", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const okm = await cryptoFunctionService.hkdfExpand(
|
||||
Utils.fromB64ToArray(b64prk),
|
||||
info,
|
||||
outputByteSize,
|
||||
algorithm
|
||||
);
|
||||
expect(Utils.fromBufferToB64(okm)).toBe(b64ExpectedOkm);
|
||||
});
|
||||
}
|
||||
|
||||
function testHash(
|
||||
algorithm: "sha1" | "sha256" | "sha512" | "md5",
|
||||
regularHash: string,
|
||||
utf8Hash: string,
|
||||
unicodeHash: string
|
||||
) {
|
||||
const regularValue = "HashMe!!";
|
||||
const utf8Value = "HǻshMe!!";
|
||||
const unicodeValue = "😀HashMe!!!🙏";
|
||||
|
||||
it("should create valid " + algorithm + " hash from regular input", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const hash = await cryptoFunctionService.hash(regularValue, algorithm);
|
||||
expect(Utils.fromBufferToHex(hash)).toBe(regularHash);
|
||||
});
|
||||
|
||||
it("should create valid " + algorithm + " hash from utf8 input", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const hash = await cryptoFunctionService.hash(utf8Value, algorithm);
|
||||
expect(Utils.fromBufferToHex(hash)).toBe(utf8Hash);
|
||||
});
|
||||
|
||||
it("should create valid " + algorithm + " hash from unicode input", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const hash = await cryptoFunctionService.hash(unicodeValue, algorithm);
|
||||
expect(Utils.fromBufferToHex(hash)).toBe(unicodeHash);
|
||||
});
|
||||
|
||||
it("should create valid " + algorithm + " hash from array buffer input", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const hash = await cryptoFunctionService.hash(
|
||||
Utils.fromUtf8ToArray(regularValue).buffer,
|
||||
algorithm
|
||||
);
|
||||
expect(Utils.fromBufferToHex(hash)).toBe(regularHash);
|
||||
});
|
||||
}
|
||||
|
||||
function testHmac(algorithm: "sha1" | "sha256" | "sha512", mac: string) {
|
||||
it("should create valid " + algorithm + " hmac", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const computedMac = await cryptoFunctionService.hmac(
|
||||
Utils.fromUtf8ToArray("SignMe!!").buffer,
|
||||
Utils.fromUtf8ToArray("secretkey").buffer,
|
||||
algorithm
|
||||
);
|
||||
expect(Utils.fromBufferToHex(computedMac)).toBe(mac);
|
||||
});
|
||||
}
|
||||
|
||||
function testHmacFast(algorithm: "sha1" | "sha256" | "sha512", mac: string) {
|
||||
it("should create valid " + algorithm + " hmac", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const keyByteString = Utils.fromBufferToByteString(Utils.fromUtf8ToArray("secretkey").buffer);
|
||||
const dataByteString = Utils.fromBufferToByteString(Utils.fromUtf8ToArray("SignMe!!").buffer);
|
||||
const computedMac = await cryptoFunctionService.hmacFast(
|
||||
dataByteString,
|
||||
keyByteString,
|
||||
algorithm
|
||||
);
|
||||
expect(Utils.fromBufferToHex(Utils.fromByteStringToArray(computedMac).buffer)).toBe(mac);
|
||||
});
|
||||
}
|
||||
|
||||
function testRsaGenerateKeyPair(length: 1024 | 2048 | 4096) {
|
||||
it(
|
||||
"should successfully generate a " + length + " bit key pair",
|
||||
async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const keyPair = await cryptoFunctionService.rsaGenerateKeyPair(length);
|
||||
expect(keyPair[0] == null || keyPair[1] == null).toBe(false);
|
||||
const publicKey = await cryptoFunctionService.rsaExtractPublicKey(keyPair[1]);
|
||||
expect(Utils.fromBufferToB64(keyPair[0])).toBe(Utils.fromBufferToB64(publicKey));
|
||||
},
|
||||
30000
|
||||
);
|
||||
}
|
||||
|
||||
function getWebCryptoFunctionService() {
|
||||
const platformUtilsMock = Substitute.for<PlatformUtilsService>();
|
||||
platformUtilsMock.isEdge().mimicks(() => navigator.userAgent.indexOf(" Edg/") !== -1);
|
||||
|
||||
return new WebCryptoFunctionService(window);
|
||||
}
|
||||
|
||||
function makeStaticByteArray(length: number) {
|
||||
const arr = new Uint8Array(length);
|
||||
for (let i = 0; i < length; i++) {
|
||||
arr[i] = i;
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
@@ -1,356 +0,0 @@
|
||||
import * as forge from "node-forge";
|
||||
|
||||
import { CryptoFunctionService } from "../abstractions/cryptoFunction.service";
|
||||
import { Utils } from "../misc/utils";
|
||||
import { DecryptParameters } from "../models/domain/decryptParameters";
|
||||
import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
|
||||
|
||||
export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||
private crypto: Crypto;
|
||||
private subtle: SubtleCrypto;
|
||||
|
||||
constructor(win: Window) {
|
||||
this.crypto = typeof win.crypto !== "undefined" ? win.crypto : null;
|
||||
this.subtle =
|
||||
!!this.crypto && typeof win.crypto.subtle !== "undefined" ? win.crypto.subtle : null;
|
||||
}
|
||||
|
||||
async pbkdf2(
|
||||
password: string | ArrayBuffer,
|
||||
salt: string | ArrayBuffer,
|
||||
algorithm: "sha256" | "sha512",
|
||||
iterations: number
|
||||
): Promise<ArrayBuffer> {
|
||||
const wcLen = algorithm === "sha256" ? 256 : 512;
|
||||
const passwordBuf = this.toBuf(password);
|
||||
const saltBuf = this.toBuf(salt);
|
||||
|
||||
const pbkdf2Params: Pbkdf2Params = {
|
||||
name: "PBKDF2",
|
||||
salt: saltBuf,
|
||||
iterations: iterations,
|
||||
hash: { name: this.toWebCryptoAlgorithm(algorithm) },
|
||||
};
|
||||
|
||||
const impKey = await this.subtle.importKey(
|
||||
"raw",
|
||||
passwordBuf,
|
||||
{ name: "PBKDF2" } as any,
|
||||
false,
|
||||
["deriveBits"]
|
||||
);
|
||||
return await this.subtle.deriveBits(pbkdf2Params, impKey, wcLen);
|
||||
}
|
||||
|
||||
async hkdf(
|
||||
ikm: ArrayBuffer,
|
||||
salt: string | ArrayBuffer,
|
||||
info: string | ArrayBuffer,
|
||||
outputByteSize: number,
|
||||
algorithm: "sha256" | "sha512"
|
||||
): Promise<ArrayBuffer> {
|
||||
const saltBuf = this.toBuf(salt);
|
||||
const infoBuf = this.toBuf(info);
|
||||
|
||||
const hkdfParams: HkdfParams = {
|
||||
name: "HKDF",
|
||||
salt: saltBuf,
|
||||
info: infoBuf,
|
||||
hash: { name: this.toWebCryptoAlgorithm(algorithm) },
|
||||
};
|
||||
|
||||
const impKey = await this.subtle.importKey("raw", ikm, { name: "HKDF" } as any, false, [
|
||||
"deriveBits",
|
||||
]);
|
||||
return await this.subtle.deriveBits(hkdfParams as any, impKey, outputByteSize * 8);
|
||||
}
|
||||
|
||||
// ref: https://tools.ietf.org/html/rfc5869
|
||||
async hkdfExpand(
|
||||
prk: ArrayBuffer,
|
||||
info: string | ArrayBuffer,
|
||||
outputByteSize: number,
|
||||
algorithm: "sha256" | "sha512"
|
||||
): Promise<ArrayBuffer> {
|
||||
const hashLen = algorithm === "sha256" ? 32 : 64;
|
||||
if (outputByteSize > 255 * hashLen) {
|
||||
throw new Error("outputByteSize is too large.");
|
||||
}
|
||||
const prkArr = new Uint8Array(prk);
|
||||
if (prkArr.length < hashLen) {
|
||||
throw new Error("prk is too small.");
|
||||
}
|
||||
const infoBuf = this.toBuf(info);
|
||||
const infoArr = new Uint8Array(infoBuf);
|
||||
let runningOkmLength = 0;
|
||||
let previousT = new Uint8Array(0);
|
||||
const n = Math.ceil(outputByteSize / hashLen);
|
||||
const okm = new Uint8Array(n * hashLen);
|
||||
for (let i = 0; i < n; i++) {
|
||||
const t = new Uint8Array(previousT.length + infoArr.length + 1);
|
||||
t.set(previousT);
|
||||
t.set(infoArr, previousT.length);
|
||||
t.set([i + 1], t.length - 1);
|
||||
previousT = new Uint8Array(await this.hmac(t.buffer, prk, algorithm));
|
||||
okm.set(previousT, runningOkmLength);
|
||||
runningOkmLength += previousT.length;
|
||||
if (runningOkmLength >= outputByteSize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return okm.slice(0, outputByteSize).buffer;
|
||||
}
|
||||
|
||||
async hash(
|
||||
value: string | ArrayBuffer,
|
||||
algorithm: "sha1" | "sha256" | "sha512" | "md5"
|
||||
): Promise<ArrayBuffer> {
|
||||
if (algorithm === "md5") {
|
||||
const md = algorithm === "md5" ? forge.md.md5.create() : forge.md.sha1.create();
|
||||
const valueBytes = this.toByteString(value);
|
||||
md.update(valueBytes, "raw");
|
||||
return Utils.fromByteStringToArray(md.digest().data).buffer;
|
||||
}
|
||||
|
||||
const valueBuf = this.toBuf(value);
|
||||
return await this.subtle.digest({ name: this.toWebCryptoAlgorithm(algorithm) }, valueBuf);
|
||||
}
|
||||
|
||||
async hmac(
|
||||
value: ArrayBuffer,
|
||||
key: ArrayBuffer,
|
||||
algorithm: "sha1" | "sha256" | "sha512"
|
||||
): Promise<ArrayBuffer> {
|
||||
const signingAlgorithm = {
|
||||
name: "HMAC",
|
||||
hash: { name: this.toWebCryptoAlgorithm(algorithm) },
|
||||
};
|
||||
|
||||
const impKey = await this.subtle.importKey("raw", key, signingAlgorithm, false, ["sign"]);
|
||||
return await this.subtle.sign(signingAlgorithm, impKey, value);
|
||||
}
|
||||
|
||||
// Safely compare two values in a way that protects against timing attacks (Double HMAC Verification).
|
||||
// ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
|
||||
// ref: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy
|
||||
async compare(a: ArrayBuffer, b: ArrayBuffer): Promise<boolean> {
|
||||
const macKey = await this.randomBytes(32);
|
||||
const signingAlgorithm = {
|
||||
name: "HMAC",
|
||||
hash: { name: "SHA-256" },
|
||||
};
|
||||
const impKey = await this.subtle.importKey("raw", macKey, signingAlgorithm, false, ["sign"]);
|
||||
const mac1 = await this.subtle.sign(signingAlgorithm, impKey, a);
|
||||
const mac2 = await this.subtle.sign(signingAlgorithm, impKey, b);
|
||||
|
||||
if (mac1.byteLength !== mac2.byteLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const arr1 = new Uint8Array(mac1);
|
||||
const arr2 = new Uint8Array(mac2);
|
||||
for (let i = 0; i < arr2.length; i++) {
|
||||
if (arr1[i] !== arr2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
hmacFast(value: string, key: string, algorithm: "sha1" | "sha256" | "sha512"): Promise<string> {
|
||||
const hmac = forge.hmac.create();
|
||||
hmac.start(algorithm, key);
|
||||
hmac.update(value);
|
||||
const bytes = hmac.digest().getBytes();
|
||||
return Promise.resolve(bytes);
|
||||
}
|
||||
|
||||
async compareFast(a: string, b: string): Promise<boolean> {
|
||||
const rand = await this.randomBytes(32);
|
||||
const bytes = new Uint32Array(rand);
|
||||
const buffer = forge.util.createBuffer();
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
buffer.putInt32(bytes[i]);
|
||||
}
|
||||
const macKey = buffer.getBytes();
|
||||
|
||||
const hmac = forge.hmac.create();
|
||||
hmac.start("sha256", macKey);
|
||||
hmac.update(a);
|
||||
const mac1 = hmac.digest().getBytes();
|
||||
|
||||
hmac.start(null, null);
|
||||
hmac.update(b);
|
||||
const mac2 = hmac.digest().getBytes();
|
||||
|
||||
const equals = mac1 === mac2;
|
||||
return equals;
|
||||
}
|
||||
|
||||
async aesEncrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
|
||||
const impKey = await this.subtle.importKey("raw", key, { name: "AES-CBC" } as any, false, [
|
||||
"encrypt",
|
||||
]);
|
||||
return await this.subtle.encrypt({ name: "AES-CBC", iv: iv }, impKey, data);
|
||||
}
|
||||
|
||||
aesDecryptFastParameters(
|
||||
data: string,
|
||||
iv: string,
|
||||
mac: string,
|
||||
key: SymmetricCryptoKey
|
||||
): DecryptParameters<string> {
|
||||
const p = new DecryptParameters<string>();
|
||||
if (key.meta != null) {
|
||||
p.encKey = key.meta.encKeyByteString;
|
||||
p.macKey = key.meta.macKeyByteString;
|
||||
}
|
||||
|
||||
if (p.encKey == null) {
|
||||
p.encKey = forge.util.decode64(key.encKeyB64);
|
||||
}
|
||||
p.data = forge.util.decode64(data);
|
||||
p.iv = forge.util.decode64(iv);
|
||||
p.macData = p.iv + p.data;
|
||||
if (p.macKey == null && key.macKeyB64 != null) {
|
||||
p.macKey = forge.util.decode64(key.macKeyB64);
|
||||
}
|
||||
if (mac != null) {
|
||||
p.mac = forge.util.decode64(mac);
|
||||
}
|
||||
|
||||
// cache byte string keys for later
|
||||
if (key.meta == null) {
|
||||
key.meta = {};
|
||||
}
|
||||
if (key.meta.encKeyByteString == null) {
|
||||
key.meta.encKeyByteString = p.encKey;
|
||||
}
|
||||
if (p.macKey != null && key.meta.macKeyByteString == null) {
|
||||
key.meta.macKeyByteString = p.macKey;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
aesDecryptFast(parameters: DecryptParameters<string>): Promise<string> {
|
||||
const dataBuffer = forge.util.createBuffer(parameters.data);
|
||||
const decipher = forge.cipher.createDecipher("AES-CBC", parameters.encKey);
|
||||
decipher.start({ iv: parameters.iv });
|
||||
decipher.update(dataBuffer);
|
||||
decipher.finish();
|
||||
const val = decipher.output.toString();
|
||||
return Promise.resolve(val);
|
||||
}
|
||||
|
||||
async aesDecrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
|
||||
const impKey = await this.subtle.importKey("raw", key, { name: "AES-CBC" } as any, false, [
|
||||
"decrypt",
|
||||
]);
|
||||
return await this.subtle.decrypt({ name: "AES-CBC", iv: iv }, impKey, data);
|
||||
}
|
||||
|
||||
async rsaEncrypt(
|
||||
data: ArrayBuffer,
|
||||
publicKey: ArrayBuffer,
|
||||
algorithm: "sha1" | "sha256"
|
||||
): Promise<ArrayBuffer> {
|
||||
// Note: Edge browser requires that we specify name and hash for both key import and decrypt.
|
||||
// We cannot use the proper types here.
|
||||
const rsaParams = {
|
||||
name: "RSA-OAEP",
|
||||
hash: { name: this.toWebCryptoAlgorithm(algorithm) },
|
||||
};
|
||||
const impKey = await this.subtle.importKey("spki", publicKey, rsaParams, false, ["encrypt"]);
|
||||
return await this.subtle.encrypt(rsaParams, impKey, data);
|
||||
}
|
||||
|
||||
async rsaDecrypt(
|
||||
data: ArrayBuffer,
|
||||
privateKey: ArrayBuffer,
|
||||
algorithm: "sha1" | "sha256"
|
||||
): Promise<ArrayBuffer> {
|
||||
// Note: Edge browser requires that we specify name and hash for both key import and decrypt.
|
||||
// We cannot use the proper types here.
|
||||
const rsaParams = {
|
||||
name: "RSA-OAEP",
|
||||
hash: { name: this.toWebCryptoAlgorithm(algorithm) },
|
||||
};
|
||||
const impKey = await this.subtle.importKey("pkcs8", privateKey, rsaParams, false, ["decrypt"]);
|
||||
return await this.subtle.decrypt(rsaParams, impKey, data);
|
||||
}
|
||||
|
||||
async rsaExtractPublicKey(privateKey: ArrayBuffer): Promise<ArrayBuffer> {
|
||||
const rsaParams = {
|
||||
name: "RSA-OAEP",
|
||||
// Have to specify some algorithm
|
||||
hash: { name: this.toWebCryptoAlgorithm("sha1") },
|
||||
};
|
||||
const impPrivateKey = await this.subtle.importKey("pkcs8", privateKey, rsaParams, true, [
|
||||
"decrypt",
|
||||
]);
|
||||
const jwkPrivateKey = await this.subtle.exportKey("jwk", impPrivateKey);
|
||||
const jwkPublicKeyParams = {
|
||||
kty: "RSA",
|
||||
e: jwkPrivateKey.e,
|
||||
n: jwkPrivateKey.n,
|
||||
alg: "RSA-OAEP",
|
||||
ext: true,
|
||||
};
|
||||
const impPublicKey = await this.subtle.importKey("jwk", jwkPublicKeyParams, rsaParams, true, [
|
||||
"encrypt",
|
||||
]);
|
||||
return await this.subtle.exportKey("spki", impPublicKey);
|
||||
}
|
||||
|
||||
async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[ArrayBuffer, ArrayBuffer]> {
|
||||
const rsaParams = {
|
||||
name: "RSA-OAEP",
|
||||
modulusLength: length,
|
||||
publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 65537
|
||||
// Have to specify some algorithm
|
||||
hash: { name: this.toWebCryptoAlgorithm("sha1") },
|
||||
};
|
||||
const keyPair = (await this.subtle.generateKey(rsaParams, true, [
|
||||
"encrypt",
|
||||
"decrypt",
|
||||
])) as CryptoKeyPair;
|
||||
const publicKey = await this.subtle.exportKey("spki", keyPair.publicKey);
|
||||
const privateKey = await this.subtle.exportKey("pkcs8", keyPair.privateKey);
|
||||
return [publicKey, privateKey];
|
||||
}
|
||||
|
||||
randomBytes(length: number): Promise<ArrayBuffer> {
|
||||
const arr = new Uint8Array(length);
|
||||
this.crypto.getRandomValues(arr);
|
||||
return Promise.resolve(arr.buffer);
|
||||
}
|
||||
|
||||
private toBuf(value: string | ArrayBuffer): ArrayBuffer {
|
||||
let buf: ArrayBuffer;
|
||||
if (typeof value === "string") {
|
||||
buf = Utils.fromUtf8ToArray(value).buffer;
|
||||
} else {
|
||||
buf = value;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
private toByteString(value: string | ArrayBuffer): string {
|
||||
let bytes: string;
|
||||
if (typeof value === "string") {
|
||||
bytes = forge.util.encodeUtf8(value);
|
||||
} else {
|
||||
bytes = Utils.fromBufferToByteString(value);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private toWebCryptoAlgorithm(algorithm: "sha1" | "sha256" | "sha512" | "md5"): string {
|
||||
if (algorithm === "md5") {
|
||||
throw new Error("MD5 is not supported in WebCrypto.");
|
||||
}
|
||||
return algorithm === "sha1" ? "SHA-1" : algorithm === "sha256" ? "SHA-256" : "SHA-512";
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { webcrypto } from "crypto";
|
||||
|
||||
import { toEqualBuffer } from "./spec";
|
||||
|
||||
Object.defineProperty(window, "crypto", {
|
||||
value: webcrypto,
|
||||
});
|
||||
|
||||
// Add custom matchers
|
||||
|
||||
expect.extend({
|
||||
toEqualBuffer: toEqualBuffer,
|
||||
});
|
||||
|
||||
export interface CustomMatchers<R = unknown> {
|
||||
toEqualBuffer(expected: Uint8Array | ArrayBuffer): R;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
const { pathsToModuleNameMapper } = require("ts-jest");
|
||||
|
||||
const { compilerOptions } = require("../shared/tsconfig.libs");
|
||||
|
||||
const sharedConfig = require("../shared/jest.config.ts");
|
||||
|
||||
/** @type {import('jest').Config} */
|
||||
module.exports = {
|
||||
...sharedConfig,
|
||||
displayName: "libs/electron tests",
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "node",
|
||||
setupFilesAfterEnv: ["<rootDir>/test.setup.ts"],
|
||||
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
|
||||
prefix: "<rootDir>/",
|
||||
}),
|
||||
};
|
||||
@@ -1,17 +0,0 @@
|
||||
const { pathsToModuleNameMapper } = require("ts-jest");
|
||||
|
||||
const { compilerOptions } = require("../shared/tsconfig.libs");
|
||||
|
||||
const sharedConfig = require("../shared/jest.config.ts");
|
||||
|
||||
/** @type {import('jest').Config} */
|
||||
module.exports = {
|
||||
...sharedConfig,
|
||||
displayName: "libs/node tests",
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "node",
|
||||
setupFilesAfterEnv: ["<rootDir>/test.setup.ts"],
|
||||
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
|
||||
prefix: "<rootDir>/",
|
||||
}),
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
import * as ts from "typescript";
|
||||
|
||||
// Custom Typescript AST transformer for use with ts-jest / jest-preset-angular
|
||||
// Removes specified ES2020 syntax from source code, as node does not support it yet
|
||||
// Reference: https://kulshekhar.github.io/ts-jest/docs/getting-started/options/astTransformers
|
||||
// Use this tool to understand how we identify and filter AST nodes: https://ts-ast-viewer.com/
|
||||
|
||||
/**
|
||||
* Remember to increase the version whenever transformer's content is changed. This is to inform Jest to not reuse
|
||||
* the previous cache which contains old transformer's content
|
||||
*/
|
||||
export const version = 1;
|
||||
export const name = "bit-es2020-transformer";
|
||||
|
||||
// Returns true for 'import.meta' statements
|
||||
const isImportMetaStatement = (node: ts.Node) =>
|
||||
ts.isPropertyAccessExpression(node) &&
|
||||
ts.isMetaProperty(node.expression) &&
|
||||
node.expression.keywordToken === ts.SyntaxKind.ImportKeyword;
|
||||
|
||||
export const factory = function (/*opts?: Opts*/) {
|
||||
function visitor(ctx: ts.TransformationContext, sf: ts.SourceFile) {
|
||||
const visitor: ts.Visitor = (node: ts.Node): ts.VisitResult<any> => {
|
||||
if (isImportMetaStatement(node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Continue searching child nodes
|
||||
return ts.visitEachChild(node, visitor, ctx);
|
||||
};
|
||||
return visitor;
|
||||
}
|
||||
return (ctx: ts.TransformationContext): ts.Transformer<any> => {
|
||||
return (sf: ts.SourceFile) => ts.visitNode(sf, visitor(ctx, sf));
|
||||
};
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
/* eslint-env node */
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const { defaultTransformerOptions } = require("jest-preset-angular/presets");
|
||||
|
||||
/** @type {import('jest').Config} */
|
||||
module.exports = {
|
||||
testMatch: ["**/+(*.)+(spec).+(ts)"],
|
||||
|
||||
// Workaround for a memory leak that crashes tests in CI:
|
||||
// https://github.com/facebook/jest/issues/9430#issuecomment-1149882002
|
||||
// Also anecdotally improves performance when run locally
|
||||
maxWorkers: 3,
|
||||
|
||||
transform: {
|
||||
"^.+\\.(ts|js|mjs|svg)$": [
|
||||
"jest-preset-angular",
|
||||
{
|
||||
...defaultTransformerOptions,
|
||||
// Jest does not use tsconfig.spec.json by default
|
||||
tsconfig: "<rootDir>/tsconfig.spec.json",
|
||||
// Further workaround for memory leak, recommended here:
|
||||
// https://github.com/kulshekhar/ts-jest/issues/1967#issuecomment-697494014
|
||||
// Makes tests run faster and reduces size/rate of leak, but loses typechecking on test code
|
||||
// See https://bitwarden.atlassian.net/browse/EC-497 for more info
|
||||
isolatedModules: true,
|
||||
astTransformers: {
|
||||
before: ["<rootDir>/../../jslib/shared/es2020-transformer.ts"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -1,29 +0,0 @@
|
||||
/* eslint-env node */
|
||||
|
||||
/** @type {import('jest').Config} */
|
||||
module.exports = {
|
||||
testMatch: ["**/+(*.)+(spec).+(ts)"],
|
||||
|
||||
// Workaround for a memory leak that crashes tests in CI:
|
||||
// https://github.com/facebook/jest/issues/9430#issuecomment-1149882002
|
||||
// Also anecdotally improves performance when run locally
|
||||
maxWorkers: 3,
|
||||
|
||||
transform: {
|
||||
"^.+\\.tsx?$": [
|
||||
"ts-jest",
|
||||
{
|
||||
// Jest does not use tsconfig.spec.json by default
|
||||
tsconfig: "<rootDir>/tsconfig.spec.json",
|
||||
// Further workaround for memory leak, recommended here:
|
||||
// https://github.com/kulshekhar/ts-jest/issues/1967#issuecomment-697494014
|
||||
// Makes tests run faster and reduces size/rate of leak, but loses typechecking on test code
|
||||
// See https://bitwarden.atlassian.net/browse/EC-497 for more info
|
||||
isolatedModules: true,
|
||||
astTransformers: {
|
||||
before: ["<rootDir>/../../jslib/shared/es2020-transformer.ts"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
import JSDOMEnvironment from "jest-environment-jsdom";
|
||||
|
||||
/**
|
||||
* https://github.com/jsdom/jsdom/issues/3363#issuecomment-1467894943
|
||||
* Adds nodes structuredClone implementation to the global object of jsdom.
|
||||
* use by either adding this file to the testEnvironment property of jest config
|
||||
* or by adding the following to the top spec file:
|
||||
*
|
||||
* ```
|
||||
* /**
|
||||
* * @jest-environment ../shared/test.environment.ts
|
||||
* *\/
|
||||
* ```
|
||||
*/
|
||||
export default class FixJSDOMEnvironment extends JSDOMEnvironment {
|
||||
constructor(...args: ConstructorParameters<typeof JSDOMEnvironment>) {
|
||||
super(...args);
|
||||
|
||||
// FIXME https://github.com/jsdom/jsdom/issues/3363
|
||||
this.global.structuredClone = structuredClone;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig",
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"tldjs": ["../common/src/misc/tldjs.noop"],
|
||||
"jslib-common/*": ["../common/src/*"],
|
||||
"jslib-angular/*": ["../angular/src/*"],
|
||||
"jslib-electron/*": ["../electron/src/*"],
|
||||
"jslib-node/*": ["../node/src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,8 @@
|
||||
"prepare": "husky install",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:watch:all": "jest --watchAll"
|
||||
"test:watch:all": "jest --watchAll",
|
||||
"test:types": "npx tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "15.2.9",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { webcrypto } from "crypto";
|
||||
|
||||
import "jest-preset-angular/setup-jest";
|
||||
|
||||
Object.defineProperty(window, "CSS", { value: null });
|
||||
@@ -24,5 +24,5 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": ["src", "jslib", "scripts"]
|
||||
"include": ["src", "jslib", "scripts", "./*.ts"]
|
||||
}
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
"baseUrl": ".",
|
||||
"resolveJsonModule": true,
|
||||
"paths": {
|
||||
"tldjs": ["@/jslib/src/misc/tldjs.noop"],
|
||||
"tldjs": ["./jslib/common/src/misc/tldjs.noop"],
|
||||
"@/*": ["./*"]
|
||||
},
|
||||
"useDefineForClassFields": false
|
||||
},
|
||||
"include": ["src", "src-cli", "jslib"]
|
||||
"include": ["src", "jslib"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user