mirror of
https://github.com/bitwarden/directory-connector
synced 2025-12-22 11:13:30 +00:00
Compare commits
8 Commits
jared/type
...
renovate/j
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffa78ab03f | ||
|
|
d1ac1e667e | ||
|
|
b9867b131f | ||
|
|
bb165441ee | ||
|
|
b8964aa382 | ||
|
|
db5268ccd1 | ||
|
|
9a719c9e4e | ||
|
|
2f49f4d5f1 |
@@ -1,27 +0,0 @@
|
||||
Please review this pull request with a focus on:
|
||||
|
||||
- Code quality and best practices
|
||||
- Potential bugs or issues
|
||||
- Security implications
|
||||
- Performance considerations
|
||||
|
||||
Note: The PR branch is already checked out in the current working directory.
|
||||
|
||||
Provide a comprehensive review including:
|
||||
|
||||
- Summary of changes since last review
|
||||
- Critical issues found (be thorough)
|
||||
- Suggested improvements (be thorough)
|
||||
- Good practices observed (be concise - list only the most notable items without elaboration)
|
||||
- Action items for the author
|
||||
- Leverage collapsible <details> sections where appropriate for lengthy explanations or code
|
||||
snippets to enhance human readability
|
||||
|
||||
When reviewing subsequent commits:
|
||||
|
||||
- Track status of previously identified issues (fixed/unfixed/reopened)
|
||||
- Identify NEW problems introduced since last review
|
||||
- Note if fixes introduced new issues
|
||||
|
||||
IMPORTANT: Be comprehensive about issues and improvements. For good practices, be brief - just note
|
||||
what was done well without explaining why or praising excessively.
|
||||
@@ -1,11 +0,0 @@
|
||||
dist
|
||||
build
|
||||
build-cli
|
||||
coverage
|
||||
webpack.cli.js
|
||||
webpack.main.js
|
||||
webpack.renderer.js
|
||||
|
||||
**/node_modules
|
||||
|
||||
**/jest.config.js
|
||||
@@ -1,95 +0,0 @@
|
||||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.js"],
|
||||
"plugins": ["@typescript-eslint", "rxjs", "rxjs-angular", "import"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": ["./tsconfig.eslint.json"],
|
||||
"sourceType": "module",
|
||||
"ecmaVersion": 2020
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:import/recommended",
|
||||
"plugin:import/typescript",
|
||||
"prettier",
|
||||
"plugin:rxjs/recommended"
|
||||
],
|
||||
"settings": {
|
||||
"import/parsers": {
|
||||
"@typescript-eslint/parser": [".ts"]
|
||||
},
|
||||
"import/resolver": {
|
||||
"typescript": {
|
||||
"alwaysTryTypes": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"@typescript-eslint/explicit-member-accessibility": [
|
||||
"error",
|
||||
{ "accessibility": "no-public" }
|
||||
],
|
||||
"@typescript-eslint/no-explicit-any": "off", // TODO: This should be re-enabled
|
||||
"@typescript-eslint/no-misused-promises": ["error", { "checksVoidReturn": false }],
|
||||
"@typescript-eslint/no-this-alias": ["error", { "allowedNames": ["self"] }],
|
||||
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
|
||||
"no-console": "error",
|
||||
"import/no-unresolved": "off", // TODO: Look into turning off once each package is an actual package.
|
||||
"import/order": [
|
||||
"error",
|
||||
{
|
||||
"alphabetize": {
|
||||
"order": "asc"
|
||||
},
|
||||
"newlines-between": "always",
|
||||
"pathGroups": [
|
||||
{
|
||||
"pattern": "@/jslib/**/*",
|
||||
"group": "external",
|
||||
"position": "after"
|
||||
},
|
||||
{
|
||||
"pattern": "@/src/**/*",
|
||||
"group": "parent",
|
||||
"position": "before"
|
||||
}
|
||||
],
|
||||
"pathGroupsExcludedImportTypes": ["builtin"]
|
||||
}
|
||||
],
|
||||
"rxjs-angular/prefer-takeuntil": "error",
|
||||
"rxjs/no-exposed-subjects": ["error", { "allowProtected": true }],
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
"message": "Calling `svgIcon` directly is not allowed",
|
||||
"selector": "CallExpression[callee.name='svgIcon']"
|
||||
},
|
||||
{
|
||||
"message": "Accessing FormGroup using `get` is not allowed, use `.value` instead",
|
||||
"selector": "ChainExpression[expression.object.callee.property.name='get'][expression.property.name='value']"
|
||||
}
|
||||
],
|
||||
"curly": ["error", "all"],
|
||||
"import/namespace": ["off"], // This doesn't resolve namespace imports correctly, but TS will throw for this anyway
|
||||
"no-restricted-imports": ["error", { "patterns": ["src/**/*"] }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["*.html"],
|
||||
"parser": "@angular-eslint/template-parser",
|
||||
"plugins": ["@angular-eslint/template"],
|
||||
"rules": {
|
||||
"@angular-eslint/template/button-has-type": "error"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
149
eslint.config.mjs
Normal file
149
eslint.config.mjs
Normal file
@@ -0,0 +1,149 @@
|
||||
// @ts-check
|
||||
import eslint from "@eslint/js";
|
||||
import tsParser from "@typescript-eslint/parser";
|
||||
import tsPlugin from "@typescript-eslint/eslint-plugin";
|
||||
import prettierConfig from "eslint-config-prettier";
|
||||
import importPlugin from "eslint-plugin-import";
|
||||
import rxjsX from "eslint-plugin-rxjs-x";
|
||||
import rxjsAngularX from "eslint-plugin-rxjs-angular-x";
|
||||
import angularEslint from "@angular-eslint/eslint-plugin-template";
|
||||
import angularParser from "@angular-eslint/template-parser";
|
||||
import globals from "globals";
|
||||
|
||||
export default [
|
||||
// Global ignores (replaces .eslintignore)
|
||||
{
|
||||
ignores: [
|
||||
"dist/**",
|
||||
"dist-cli/**",
|
||||
"build/**",
|
||||
"build-cli/**",
|
||||
"coverage/**",
|
||||
"**/*.cjs",
|
||||
"eslint.config.mjs",
|
||||
"scripts/**/*.js",
|
||||
"**/node_modules/**",
|
||||
],
|
||||
},
|
||||
|
||||
// Base config for all JavaScript/TypeScript files
|
||||
{
|
||||
files: ["**/*.ts", "**/*.js"],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
sourceType: "module",
|
||||
parser: tsParser,
|
||||
parserOptions: {
|
||||
project: ["./tsconfig.eslint.json"],
|
||||
},
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
"@typescript-eslint": tsPlugin,
|
||||
import: importPlugin,
|
||||
"rxjs-x": rxjsX,
|
||||
"rxjs-angular-x": rxjsAngularX,
|
||||
},
|
||||
settings: {
|
||||
"import/parsers": {
|
||||
"@typescript-eslint/parser": [".ts"],
|
||||
},
|
||||
"import/resolver": {
|
||||
typescript: {
|
||||
alwaysTryTypes: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
// ESLint recommended rules
|
||||
...eslint.configs.recommended.rules,
|
||||
|
||||
// TypeScript ESLint recommended rules
|
||||
...tsPlugin.configs.recommended.rules,
|
||||
|
||||
// Import plugin recommended rules
|
||||
...importPlugin.flatConfigs.recommended.rules,
|
||||
|
||||
// RxJS recommended rules
|
||||
...rxjsX.configs.recommended.rules,
|
||||
|
||||
// Custom project rules
|
||||
"@typescript-eslint/explicit-member-accessibility": ["error", { accessibility: "no-public" }],
|
||||
"@typescript-eslint/no-explicit-any": "off", // TODO: This should be re-enabled
|
||||
"@typescript-eslint/no-misused-promises": ["error", { checksVoidReturn: false }],
|
||||
"@typescript-eslint/no-this-alias": ["error", { allowedNames: ["self"] }],
|
||||
"@typescript-eslint/no-unused-vars": ["error", { args: "none" }],
|
||||
"no-console": "error",
|
||||
"import/no-unresolved": "off", // TODO: Look into turning on once each package is an actual package.
|
||||
"import/order": [
|
||||
"error",
|
||||
{
|
||||
alphabetize: {
|
||||
order: "asc",
|
||||
},
|
||||
"newlines-between": "always",
|
||||
pathGroups: [
|
||||
{
|
||||
pattern: "@/jslib/**/*",
|
||||
group: "external",
|
||||
position: "after",
|
||||
},
|
||||
{
|
||||
pattern: "@/src/**/*",
|
||||
group: "parent",
|
||||
position: "before",
|
||||
},
|
||||
],
|
||||
pathGroupsExcludedImportTypes: ["builtin"],
|
||||
},
|
||||
],
|
||||
"rxjs-angular-x/prefer-takeuntil": "error",
|
||||
"rxjs-x/no-exposed-subjects": ["error", { allowProtected: true }],
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
message: "Calling `svgIcon` directly is not allowed",
|
||||
selector: "CallExpression[callee.name='svgIcon']",
|
||||
},
|
||||
{
|
||||
message: "Accessing FormGroup using `get` is not allowed, use `.value` instead",
|
||||
selector:
|
||||
"ChainExpression[expression.object.callee.property.name='get'][expression.property.name='value']",
|
||||
},
|
||||
],
|
||||
curly: ["error", "all"],
|
||||
"import/namespace": ["off"], // This doesn't resolve namespace imports correctly, but TS will throw for this anyway
|
||||
"no-restricted-imports": ["error", { patterns: ["src/**/*"] }],
|
||||
},
|
||||
},
|
||||
|
||||
// Jest test files (includes any test-related files)
|
||||
{
|
||||
files: ["**/*.spec.ts", "**/test.setup.ts", "**/spec/**/*.ts", "**/utils/**/*fixtures*.ts"],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.jest,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Angular HTML templates
|
||||
{
|
||||
files: ["**/*.html"],
|
||||
languageOptions: {
|
||||
parser: angularParser,
|
||||
},
|
||||
plugins: {
|
||||
"@angular-eslint/template": angularEslint,
|
||||
},
|
||||
rules: {
|
||||
"@angular-eslint/template/button-has-type": "error",
|
||||
},
|
||||
},
|
||||
|
||||
// Prettier config (must be last to override other configs)
|
||||
prettierConfig,
|
||||
];
|
||||
@@ -26,7 +26,6 @@ module.exports = {
|
||||
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
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Observable, Subject } from "rxjs";
|
||||
import { first } from "rxjs/operators";
|
||||
import { lastValueFrom, Observable, Subject } from "rxjs";
|
||||
|
||||
export class ModalRef {
|
||||
onCreated: Observable<HTMLElement>; // Modal added to the DOM.
|
||||
@@ -45,6 +44,6 @@ export class ModalRef {
|
||||
}
|
||||
|
||||
onClosedPromise(): Promise<any> {
|
||||
return this.onClosed.pipe(first()).toPromise();
|
||||
return lastValueFrom(this.onClosed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Directive, ElementRef, Input, NgZone } from "@angular/core";
|
||||
import { take } from "rxjs/operators";
|
||||
import { take } from "rxjs";
|
||||
|
||||
import Utils from "@/jslib/common/src/misc/utils";
|
||||
import { Utils } from "@/jslib/common/src/misc/utils";
|
||||
|
||||
@Directive({
|
||||
selector: "[appAutofocus]",
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
Type,
|
||||
ViewContainerRef,
|
||||
} from "@angular/core";
|
||||
import { first } from "rxjs/operators";
|
||||
import { first, firstValueFrom } from "rxjs";
|
||||
|
||||
import { DynamicModalComponent } from "../components/modal/dynamic-modal.component";
|
||||
import { ModalInjector } from "../components/modal/modal-injector";
|
||||
@@ -58,7 +58,7 @@ export class ModalService {
|
||||
|
||||
viewContainerRef.insert(modalComponentRef.hostView);
|
||||
|
||||
await modalRef.onCreated.pipe(first()).toPromise();
|
||||
await firstValueFrom(modalRef.onCreated);
|
||||
|
||||
return [modalRef, modalComponentRef.instance.componentRef.instance];
|
||||
}
|
||||
|
||||
@@ -17,48 +17,45 @@ describe("SymmetricCryptoKey", () => {
|
||||
const key = makeStaticByteArray(32);
|
||||
const cryptoKey = new SymmetricCryptoKey(key);
|
||||
|
||||
expect(cryptoKey.encType).toBe(0);
|
||||
expect(cryptoKey.keyB64).toBe("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=");
|
||||
expect(cryptoKey.encKeyB64).toBe("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=");
|
||||
expect(cryptoKey.macKey).toBeNull();
|
||||
expect(cryptoKey.key).toBeInstanceOf(ArrayBuffer);
|
||||
expect(cryptoKey.encKey).toBeInstanceOf(ArrayBuffer);
|
||||
expect(cryptoKey.key.byteLength).toBe(32);
|
||||
expect(cryptoKey.encKey.byteLength).toBe(32);
|
||||
expect(cryptoKey).toEqual({
|
||||
encKey: key,
|
||||
encKeyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=",
|
||||
encType: 0,
|
||||
key: key,
|
||||
keyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=",
|
||||
macKey: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("AesCbc128_HmacSha256_B64", () => {
|
||||
const key = makeStaticByteArray(32);
|
||||
const cryptoKey = new SymmetricCryptoKey(key, EncryptionType.AesCbc128_HmacSha256_B64);
|
||||
|
||||
// After TS 5.9 upgrade, properties are ArrayBuffer not Uint8Array
|
||||
expect(cryptoKey.encType).toBe(1);
|
||||
expect(cryptoKey.keyB64).toBe("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=");
|
||||
expect(cryptoKey.encKeyB64).toBe("AAECAwQFBgcICQoLDA0ODw==");
|
||||
expect(cryptoKey.macKeyB64).toBe("EBESExQVFhcYGRobHB0eHw==");
|
||||
expect(cryptoKey.key).toBeInstanceOf(ArrayBuffer);
|
||||
expect(cryptoKey.encKey).toBeInstanceOf(ArrayBuffer);
|
||||
expect(cryptoKey.macKey).toBeInstanceOf(ArrayBuffer);
|
||||
expect(cryptoKey.key.byteLength).toBe(32);
|
||||
expect(cryptoKey.encKey.byteLength).toBe(16);
|
||||
expect(cryptoKey.macKey.byteLength).toBe(16);
|
||||
expect(cryptoKey).toEqual({
|
||||
encKey: key.slice(0, 16),
|
||||
encKeyB64: "AAECAwQFBgcICQoLDA0ODw==",
|
||||
encType: 1,
|
||||
key: key,
|
||||
keyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=",
|
||||
macKey: key.slice(16, 32),
|
||||
macKeyB64: "EBESExQVFhcYGRobHB0eHw==",
|
||||
});
|
||||
});
|
||||
|
||||
it("AesCbc256_HmacSha256_B64", () => {
|
||||
const key = makeStaticByteArray(64);
|
||||
const cryptoKey = new SymmetricCryptoKey(key);
|
||||
|
||||
// After TS 5.9 upgrade, properties are ArrayBuffer not Uint8Array
|
||||
expect(cryptoKey.encType).toBe(2);
|
||||
expect(cryptoKey.keyB64).toBe("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+Pw==");
|
||||
expect(cryptoKey.encKeyB64).toBe("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=");
|
||||
expect(cryptoKey.macKeyB64).toBe("ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8=");
|
||||
expect(cryptoKey.key).toBeInstanceOf(ArrayBuffer);
|
||||
expect(cryptoKey.encKey).toBeInstanceOf(ArrayBuffer);
|
||||
expect(cryptoKey.macKey).toBeInstanceOf(ArrayBuffer);
|
||||
expect(cryptoKey.key.byteLength).toBe(64);
|
||||
expect(cryptoKey.encKey.byteLength).toBe(32);
|
||||
expect(cryptoKey.macKey.byteLength).toBe(32);
|
||||
expect(cryptoKey).toEqual({
|
||||
encKey: key.slice(0, 32),
|
||||
encKeyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=",
|
||||
encType: 2,
|
||||
key: key,
|
||||
keyB64:
|
||||
"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+Pw==",
|
||||
macKey: key.slice(32, 64),
|
||||
macKeyB64: "ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8=",
|
||||
});
|
||||
});
|
||||
|
||||
it("unknown length", () => {
|
||||
|
||||
@@ -8,15 +8,12 @@ declare let console: any;
|
||||
export function interceptConsole(interceptions: any): object {
|
||||
console = {
|
||||
log: function () {
|
||||
// eslint-disable-next-line
|
||||
interceptions.log = arguments;
|
||||
},
|
||||
warn: function () {
|
||||
// eslint-disable-next-line
|
||||
interceptions.warn = arguments;
|
||||
},
|
||||
error: function () {
|
||||
// eslint-disable-next-line
|
||||
interceptions.error = arguments;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -29,6 +29,6 @@ export class NodeUtils {
|
||||
|
||||
// https://stackoverflow.com/a/31394257
|
||||
static bufferToArrayBuffer(buf: Buffer): ArrayBuffer {
|
||||
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) as ArrayBuffer;
|
||||
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-useless-escape */
|
||||
import url from "url";
|
||||
import * as url from "url";
|
||||
|
||||
import { I18nService } from "../abstractions/i18n.service";
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as tldjs from "tldjs";
|
||||
|
||||
const nodeURL = typeof window === "undefined" ? url : null;
|
||||
|
||||
class Utils {
|
||||
export class Utils {
|
||||
static inited = false;
|
||||
static isNode = false;
|
||||
static isBrowser = true;
|
||||
@@ -36,11 +36,9 @@ class Utils {
|
||||
Utils.global = Utils.isNode && !Utils.isBrowser ? global : window;
|
||||
}
|
||||
|
||||
static fromB64ToArray(str: string): Uint8Array<ArrayBuffer> {
|
||||
static fromB64ToArray(str: string): Uint8Array {
|
||||
if (Utils.isNode) {
|
||||
const buffer = Buffer.from(str, "base64");
|
||||
|
||||
return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength) as Uint8Array<ArrayBuffer>;
|
||||
return new Uint8Array(Buffer.from(str, "base64"));
|
||||
} else {
|
||||
const binaryString = window.atob(str);
|
||||
const bytes = new Uint8Array(binaryString.length);
|
||||
@@ -51,7 +49,7 @@ class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
static fromUrlB64ToArray(str: string): Uint8Array<ArrayBuffer> {
|
||||
static fromUrlB64ToArray(str: string): Uint8Array {
|
||||
return Utils.fromB64ToArray(Utils.fromUrlB64ToB64(str));
|
||||
}
|
||||
|
||||
@@ -67,11 +65,9 @@ class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
static fromUtf8ToArray(str: string): Uint8Array<ArrayBuffer> {
|
||||
static fromUtf8ToArray(str: string): Uint8Array {
|
||||
if (Utils.isNode) {
|
||||
const buffer = Buffer.from(str, "utf8");
|
||||
|
||||
return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength) as Uint8Array<ArrayBuffer>;
|
||||
return new Uint8Array(Buffer.from(str, "utf8"));
|
||||
} else {
|
||||
const strUtf8 = unescape(encodeURIComponent(str));
|
||||
const arr = new Uint8Array(strUtf8.length);
|
||||
@@ -90,16 +86,12 @@ class Utils {
|
||||
return arr;
|
||||
}
|
||||
|
||||
static fromBufferToB64(buffer: BufferSource): string {
|
||||
static fromBufferToB64(buffer: ArrayBuffer): string {
|
||||
if (Utils.isNode) {
|
||||
if (ArrayBuffer.isView(buffer)) {
|
||||
return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength).toString("base64");
|
||||
} else {
|
||||
return Buffer.from(buffer).toString("base64");
|
||||
}
|
||||
return Buffer.from(buffer).toString("base64");
|
||||
} else {
|
||||
let binary = "";
|
||||
const bytes = ArrayBuffer.isView(buffer) ? new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength) : new Uint8Array(buffer);
|
||||
const bytes = new Uint8Array(buffer);
|
||||
for (let i = 0; i < bytes.byteLength; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
@@ -107,7 +99,7 @@ class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
static fromBufferToUrlB64(buffer: BufferSource): string {
|
||||
static fromBufferToUrlB64(buffer: ArrayBuffer): string {
|
||||
return Utils.fromB64toUrlB64(Utils.fromBufferToB64(buffer));
|
||||
}
|
||||
|
||||
@@ -413,6 +405,4 @@ class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
export default Utils;
|
||||
|
||||
Utils.init();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CryptoService } from "../../abstractions/crypto.service";
|
||||
import { EncryptionType } from "../../enums/encryptionType";
|
||||
import Utils from "../../misc/utils";
|
||||
import { Utils } from "../../misc/utils";
|
||||
|
||||
import { SymmetricCryptoKey } from "./symmetricCryptoKey";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EncryptionType } from "../../enums/encryptionType";
|
||||
import Utils from "../../misc/utils";
|
||||
import { Utils } from "../../misc/utils";
|
||||
|
||||
export class SymmetricCryptoKey {
|
||||
key: ArrayBuffer;
|
||||
@@ -13,35 +13,33 @@ export class SymmetricCryptoKey {
|
||||
|
||||
meta: any;
|
||||
|
||||
constructor(key: BufferSource, encType?: EncryptionType) {
|
||||
constructor(key: ArrayBuffer, encType?: EncryptionType) {
|
||||
if (key == null) {
|
||||
throw new Error("Must provide key");
|
||||
}
|
||||
|
||||
const keyBuffer = ArrayBuffer.isView(key) ? key.buffer.slice(key.byteOffset, key.byteOffset + key.byteLength) : key;
|
||||
|
||||
if (encType == null) {
|
||||
if (keyBuffer.byteLength === 32) {
|
||||
if (key.byteLength === 32) {
|
||||
encType = EncryptionType.AesCbc256_B64;
|
||||
} else if (keyBuffer.byteLength === 64) {
|
||||
} else if (key.byteLength === 64) {
|
||||
encType = EncryptionType.AesCbc256_HmacSha256_B64;
|
||||
} else {
|
||||
throw new Error("Unable to determine encType.");
|
||||
}
|
||||
}
|
||||
|
||||
this.key = keyBuffer;
|
||||
this.key = key;
|
||||
this.encType = encType;
|
||||
|
||||
if (encType === EncryptionType.AesCbc256_B64 && keyBuffer.byteLength === 32) {
|
||||
this.encKey = keyBuffer;
|
||||
if (encType === EncryptionType.AesCbc256_B64 && key.byteLength === 32) {
|
||||
this.encKey = key;
|
||||
this.macKey = null;
|
||||
} else if (encType === EncryptionType.AesCbc128_HmacSha256_B64 && keyBuffer.byteLength === 32) {
|
||||
this.encKey = keyBuffer.slice(0, 16);
|
||||
this.macKey = keyBuffer.slice(16, 32);
|
||||
} else if (encType === EncryptionType.AesCbc256_HmacSha256_B64 && keyBuffer.byteLength === 64) {
|
||||
this.encKey = keyBuffer.slice(0, 32);
|
||||
this.macKey = keyBuffer.slice(32, 64);
|
||||
} else if (encType === EncryptionType.AesCbc128_HmacSha256_B64 && key.byteLength === 32) {
|
||||
this.encKey = key.slice(0, 16);
|
||||
this.macKey = key.slice(16, 32);
|
||||
} else if (encType === EncryptionType.AesCbc256_HmacSha256_B64 && key.byteLength === 64) {
|
||||
this.encKey = key.slice(0, 32);
|
||||
this.macKey = key.slice(32, 64);
|
||||
} else {
|
||||
throw new Error("Unsupported encType/key length.");
|
||||
}
|
||||
|
||||
@@ -29,5 +29,4 @@ export class PasswordTokenRequest extends TokenRequest implements CaptchaProtect
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ export abstract class TokenRequest {
|
||||
this.device = device != null ? device : null;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
alterIdentityTokenHeaders(headers: Headers) {
|
||||
// Implemented in subclass if required
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Utils from "../../misc/utils";
|
||||
import { Utils } from "../../misc/utils";
|
||||
|
||||
import { BaseResponse } from "./baseResponse";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Utils from "../../misc/utils";
|
||||
import { Utils } from "../../misc/utils";
|
||||
|
||||
import { BaseResponse } from "./baseResponse";
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { EnvironmentService } from "../abstractions/environment.service";
|
||||
import { PlatformUtilsService } from "../abstractions/platformUtils.service";
|
||||
import { TokenService } from "../abstractions/token.service";
|
||||
import { DeviceType } from "../enums/deviceType";
|
||||
import Utils from "../misc/utils";
|
||||
import { Utils } from "../misc/utils";
|
||||
import { ApiTokenRequest } from "../models/request/identityToken/apiTokenRequest";
|
||||
import { PasswordTokenRequest } from "../models/request/identityToken/passwordTokenRequest";
|
||||
import { SsoTokenRequest } from "../models/request/identityToken/ssoTokenRequest";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AppIdService as AppIdServiceAbstraction } from "../abstractions/appId.service";
|
||||
import { StorageService } from "../abstractions/storage.service";
|
||||
import { HtmlStorageLocation } from "../enums/htmlStorageLocation";
|
||||
import Utils from "../misc/utils";
|
||||
import { Utils } from "../misc/utils";
|
||||
|
||||
export class AppIdService implements AppIdServiceAbstraction {
|
||||
constructor(private storageService: StorageService) {}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { HashPurpose } from "../enums/hashPurpose";
|
||||
import { KdfType } from "../enums/kdfType";
|
||||
import { KeySuffixOptions } from "../enums/keySuffixOptions";
|
||||
import { sequentialize } from "../misc/sequentialize";
|
||||
import Utils from "../misc/utils";
|
||||
import { Utils } from "../misc/utils";
|
||||
import { EEFLongWordList } from "../misc/wordlist";
|
||||
import { EncArrayBuffer } from "../models/domain/encArrayBuffer";
|
||||
import { EncString } from "../models/domain/encString";
|
||||
@@ -109,7 +109,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
): Promise<SymmetricCryptoKey> {
|
||||
const key = await this.retrieveKeyFromStorage(keySuffix, userId);
|
||||
if (key != null) {
|
||||
const symmetricKey = new SymmetricCryptoKey(Utils.fromB64ToArray(key));
|
||||
const symmetricKey = new SymmetricCryptoKey(Utils.fromB64ToArray(key).buffer);
|
||||
|
||||
if (!(await this.validateKey(symmetricKey))) {
|
||||
this.logService.warning("Wrong key, throwing away stored key");
|
||||
@@ -510,9 +510,9 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
let plainBuf: BufferSource;
|
||||
let plainBuf: ArrayBuffer;
|
||||
if (typeof plainValue === "string") {
|
||||
plainBuf = Utils.fromUtf8ToArray(plainValue);
|
||||
plainBuf = Utils.fromUtf8ToArray(plainValue).buffer;
|
||||
} else {
|
||||
plainBuf = plainValue;
|
||||
}
|
||||
@@ -585,8 +585,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
throw new Error("encPieces unavailable.");
|
||||
}
|
||||
|
||||
const dataArray = Utils.fromB64ToArray(encPieces[0]);
|
||||
const data = dataArray.buffer as ArrayBuffer;
|
||||
const data = Utils.fromB64ToArray(encPieces[0]).buffer;
|
||||
const privateKey = privateKeyValue ?? (await this.getPrivateKey());
|
||||
if (privateKey == null) {
|
||||
throw new Error("No private key.");
|
||||
@@ -609,12 +608,9 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
}
|
||||
|
||||
async decryptToBytes(encString: EncString, key?: SymmetricCryptoKey): Promise<ArrayBuffer> {
|
||||
const ivArray = Utils.fromB64ToArray(encString.iv);
|
||||
const iv = ivArray.buffer as ArrayBuffer;
|
||||
const dataArray = Utils.fromB64ToArray(encString.data);
|
||||
const data = dataArray.buffer as ArrayBuffer;
|
||||
const macArray = encString.mac ? Utils.fromB64ToArray(encString.mac) : null;
|
||||
const mac = macArray ? (macArray.buffer as ArrayBuffer) : null;
|
||||
const iv = Utils.fromB64ToArray(encString.iv).buffer;
|
||||
const data = Utils.fromB64ToArray(encString.data).buffer;
|
||||
const mac = encString.mac ? Utils.fromB64ToArray(encString.mac).buffer : null;
|
||||
const decipher = await this.aesDecryptToBytes(encString.encryptionType, data, iv, mac, key);
|
||||
if (decipher == null) {
|
||||
return null;
|
||||
@@ -671,9 +667,9 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
|
||||
return await this.aesDecryptToBytes(
|
||||
encType,
|
||||
ctBytes.buffer as ArrayBuffer,
|
||||
ivBytes.buffer as ArrayBuffer,
|
||||
macBytes != null ? (macBytes.buffer as ArrayBuffer) : null,
|
||||
ctBytes.buffer,
|
||||
ivBytes.buffer,
|
||||
macBytes != null ? macBytes.buffer : null,
|
||||
key,
|
||||
);
|
||||
}
|
||||
@@ -760,24 +756,17 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
: await this.stateService.getCryptoMasterKeyBiometric({ userId: userId });
|
||||
}
|
||||
|
||||
private async aesEncrypt(data: BufferSource, key: SymmetricCryptoKey): Promise<EncryptedObject> {
|
||||
private async aesEncrypt(data: ArrayBuffer, key: SymmetricCryptoKey): Promise<EncryptedObject> {
|
||||
const obj = new EncryptedObject();
|
||||
obj.key = await this.getKeyForEncryption(key);
|
||||
obj.iv = await this.cryptoFunctionService.randomBytes(16);
|
||||
|
||||
const dataBuffer = ArrayBuffer.isView(data)
|
||||
? (data.byteOffset === 0 && data.byteLength === data.buffer.byteLength
|
||||
? data.buffer as ArrayBuffer
|
||||
: data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer)
|
||||
: data;
|
||||
obj.data = await this.cryptoFunctionService.aesEncrypt(dataBuffer, obj.iv, obj.key.encKey);
|
||||
obj.data = await this.cryptoFunctionService.aesEncrypt(data, obj.iv, obj.key.encKey);
|
||||
|
||||
if (obj.key.macKey != null) {
|
||||
const macData = new Uint8Array(obj.iv.byteLength + obj.data.byteLength);
|
||||
macData.set(new Uint8Array(obj.iv), 0);
|
||||
macData.set(new Uint8Array(obj.data), obj.iv.byteLength);
|
||||
|
||||
obj.mac = await this.cryptoFunctionService.hmac(macData.buffer as ArrayBuffer, obj.key.macKey, "sha256");
|
||||
obj.mac = await this.cryptoFunctionService.hmac(macData.buffer, obj.key.macKey, "sha256");
|
||||
}
|
||||
|
||||
return obj;
|
||||
@@ -843,7 +832,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
macData.set(new Uint8Array(iv), 0);
|
||||
macData.set(new Uint8Array(data), iv.byteLength);
|
||||
const computedMac = await this.cryptoFunctionService.hmac(
|
||||
macData.buffer as ArrayBuffer,
|
||||
macData.buffer,
|
||||
theKey.macKey,
|
||||
"sha256",
|
||||
);
|
||||
|
||||
@@ -38,8 +38,7 @@ const partialKeys = {
|
||||
export class StateService<
|
||||
TGlobalState extends GlobalState = GlobalState,
|
||||
TAccount extends Account = Account,
|
||||
> implements StateServiceAbstraction<TAccount>
|
||||
{
|
||||
> implements StateServiceAbstraction<TAccount> {
|
||||
protected accountsSubject = new BehaviorSubject<{ [userId: string]: TAccount }>({});
|
||||
accounts$ = this.accountsSubject.asObservable();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
import { TokenService as TokenServiceAbstraction } from "../abstractions/token.service";
|
||||
import Utils from "../misc/utils";
|
||||
import { Utils } from "../misc/utils";
|
||||
import { IdentityTokenResponse } from "../models/response/identityTokenResponse";
|
||||
|
||||
export class TokenService implements TokenServiceAbstraction {
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import * as path from "path";
|
||||
|
||||
import { app, BrowserWindow, Menu, MenuItemConstructorOptions, nativeImage, Tray } from "electron";
|
||||
import {
|
||||
app,
|
||||
BrowserWindow,
|
||||
Menu,
|
||||
MenuItemConstructorOptions,
|
||||
NativeImage,
|
||||
nativeImage,
|
||||
Tray,
|
||||
} from "electron";
|
||||
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
import { StateService } from "@/jslib/common/src/abstractions/state.service";
|
||||
@@ -12,8 +20,8 @@ export class TrayMain {
|
||||
|
||||
private appName: string;
|
||||
private tray: Tray;
|
||||
private icon: string | Electron.NativeImage;
|
||||
private pressedIcon: Electron.NativeImage;
|
||||
private icon: string | NativeImage;
|
||||
private pressedIcon: NativeImage;
|
||||
|
||||
constructor(
|
||||
private windowMain: WindowMain,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as path from "path";
|
||||
import * as url from "url";
|
||||
|
||||
import { app, BrowserWindow, screen } from "electron";
|
||||
import { app, BrowserWindow, Rectangle, screen } from "electron";
|
||||
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
import { StateService } from "@/jslib/common/src/abstractions/state.service";
|
||||
@@ -14,7 +14,7 @@ export class WindowMain {
|
||||
win: BrowserWindow;
|
||||
isQuitting = false;
|
||||
|
||||
private windowStateChangeTimer: NodeJS.Timeout;
|
||||
private windowStateChangeTimer: ReturnType<typeof setTimeout>;
|
||||
private windowStates: { [key: string]: any } = {};
|
||||
private enableAlwaysOnTop = false;
|
||||
|
||||
@@ -37,7 +37,6 @@ export class WindowMain {
|
||||
app.quit();
|
||||
return;
|
||||
} else {
|
||||
// eslint-disable-next-line
|
||||
app.on("second-instance", (event, argv, workingDirectory) => {
|
||||
// Someone tried to run a second instance, we should focus our window.
|
||||
if (this.win != null) {
|
||||
@@ -241,7 +240,7 @@ export class WindowMain {
|
||||
const state = await this.stateService.getWindow();
|
||||
|
||||
const isValid = state != null && (this.stateHasBounds(state) || state.isMaximized);
|
||||
let displayBounds: Electron.Rectangle = null;
|
||||
let displayBounds: Rectangle = null;
|
||||
if (!isValid) {
|
||||
state.width = defaultWidth;
|
||||
state.height = defaultHeight;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Utils from "@/jslib/common/src/misc/utils";
|
||||
import { Utils } from "@/jslib/common/src/misc/utils";
|
||||
import { SymmetricCryptoKey } from "@/jslib/common/src/models/domain/symmetricCryptoKey";
|
||||
import { NodeCryptoFunctionService } from "@/jslib/node/src/services/nodeCryptoFunction.service";
|
||||
|
||||
@@ -93,9 +93,8 @@ describe("NodeCrypto Function Service", () => {
|
||||
|
||||
it("should fail with prk too small", async () => {
|
||||
const cryptoFunctionService = new NodeCryptoFunctionService();
|
||||
const prk = Utils.fromB64ToArray(prk16Byte);
|
||||
const f = cryptoFunctionService.hkdfExpand(
|
||||
prk.buffer,
|
||||
Utils.fromB64ToArray(prk16Byte),
|
||||
"info",
|
||||
32,
|
||||
"sha256",
|
||||
@@ -105,9 +104,8 @@ describe("NodeCrypto Function Service", () => {
|
||||
|
||||
it("should fail with outputByteSize is too large", async () => {
|
||||
const cryptoFunctionService = new NodeCryptoFunctionService();
|
||||
const prk = Utils.fromB64ToArray(prk32Byte);
|
||||
const f = cryptoFunctionService.hkdfExpand(
|
||||
prk.buffer,
|
||||
Utils.fromB64ToArray(prk32Byte),
|
||||
"info",
|
||||
8161,
|
||||
"sha256",
|
||||
@@ -181,16 +179,16 @@ describe("NodeCrypto Function Service", () => {
|
||||
|
||||
it("should successfully encrypt and then decrypt data", async () => {
|
||||
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
|
||||
const iv = makeStaticByteArray(16).buffer;
|
||||
const key = makeStaticByteArray(32).buffer;
|
||||
const iv = makeStaticByteArray(16);
|
||||
const key = makeStaticByteArray(32);
|
||||
const value = "EncryptMe!";
|
||||
const data = Utils.fromUtf8ToArray(value).buffer;
|
||||
const data = Utils.fromUtf8ToArray(value);
|
||||
const encValue = await nodeCryptoFunctionService.aesEncrypt(
|
||||
data,
|
||||
iv,
|
||||
key
|
||||
data.buffer,
|
||||
iv.buffer,
|
||||
key.buffer,
|
||||
);
|
||||
const decValue = await nodeCryptoFunctionService.aesDecrypt(encValue, iv, key);
|
||||
const decValue = await nodeCryptoFunctionService.aesDecrypt(encValue, iv.buffer, key.buffer);
|
||||
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
|
||||
});
|
||||
});
|
||||
@@ -198,9 +196,8 @@ describe("NodeCrypto Function Service", () => {
|
||||
describe("aesDecryptFast", () => {
|
||||
it("should successfully decrypt data", async () => {
|
||||
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
|
||||
const ivArray = makeStaticByteArray(16);
|
||||
const iv = Utils.fromBufferToB64(ivArray);
|
||||
const symKey = new SymmetricCryptoKey(makeStaticByteArray(32));
|
||||
const iv = Utils.fromBufferToB64(makeStaticByteArray(16).buffer);
|
||||
const symKey = new SymmetricCryptoKey(makeStaticByteArray(32).buffer);
|
||||
const data = "ByUF8vhyX4ddU9gcooznwA==";
|
||||
const params = nodeCryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey);
|
||||
const decValue = await nodeCryptoFunctionService.aesDecryptFast(params);
|
||||
@@ -211,13 +208,13 @@ describe("NodeCrypto Function Service", () => {
|
||||
describe("aesDecrypt", () => {
|
||||
it("should successfully decrypt data", async () => {
|
||||
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
|
||||
const iv = makeStaticByteArray(16).buffer;
|
||||
const key = makeStaticByteArray(32).buffer;
|
||||
const data = Utils.fromB64ToArray("ByUF8vhyX4ddU9gcooznwA==").buffer;
|
||||
const iv = makeStaticByteArray(16);
|
||||
const key = makeStaticByteArray(32);
|
||||
const data = Utils.fromB64ToArray("ByUF8vhyX4ddU9gcooznwA==");
|
||||
const decValue = await nodeCryptoFunctionService.aesDecrypt(
|
||||
data,
|
||||
iv,
|
||||
key,
|
||||
data.buffer,
|
||||
iv.buffer,
|
||||
key.buffer,
|
||||
);
|
||||
expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!");
|
||||
});
|
||||
@@ -227,7 +224,7 @@ describe("NodeCrypto Function Service", () => {
|
||||
it("should successfully encrypt and then decrypt data", async () => {
|
||||
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
|
||||
const pubKey = Utils.fromB64ToArray(RsaPublicKey);
|
||||
const privKey = Utils.fromB64ToArray(RsaPrivateKey).buffer;
|
||||
const privKey = Utils.fromB64ToArray(RsaPrivateKey);
|
||||
const value = "EncryptMe!";
|
||||
const data = Utils.fromUtf8ToArray(value);
|
||||
const encValue = await nodeCryptoFunctionService.rsaEncrypt(
|
||||
@@ -235,7 +232,7 @@ describe("NodeCrypto Function Service", () => {
|
||||
pubKey.buffer,
|
||||
"sha1",
|
||||
);
|
||||
const decValue = await nodeCryptoFunctionService.rsaDecrypt(encValue, privKey, "sha1");
|
||||
const decValue = await nodeCryptoFunctionService.rsaDecrypt(encValue, privKey.buffer, "sha1");
|
||||
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
|
||||
});
|
||||
});
|
||||
@@ -262,8 +259,8 @@ describe("NodeCrypto Function Service", () => {
|
||||
describe("rsaExtractPublicKey", () => {
|
||||
it("should successfully extract key", async () => {
|
||||
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
|
||||
const privKey = Utils.fromB64ToArray(RsaPrivateKey).buffer;
|
||||
const publicKey = await nodeCryptoFunctionService.rsaExtractPublicKey(privKey);
|
||||
const privKey = Utils.fromB64ToArray(RsaPrivateKey);
|
||||
const publicKey = await nodeCryptoFunctionService.rsaExtractPublicKey(privKey.buffer);
|
||||
expect(Utils.fromBufferToB64(publicKey)).toBe(RsaPublicKey);
|
||||
});
|
||||
});
|
||||
@@ -356,26 +353,26 @@ function testHkdf(
|
||||
|
||||
it("should create valid " + algorithm + " key from regular input", async () => {
|
||||
const cryptoFunctionService = new NodeCryptoFunctionService();
|
||||
const key = await cryptoFunctionService.hkdf(ikm.buffer, regularSalt, regularInfo, 32, algorithm);
|
||||
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 = new NodeCryptoFunctionService();
|
||||
const key = await cryptoFunctionService.hkdf(ikm.buffer, utf8Salt, utf8Info, 32, algorithm);
|
||||
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 = new NodeCryptoFunctionService();
|
||||
const key = await cryptoFunctionService.hkdf(ikm.buffer, unicodeSalt, unicodeInfo, 32, algorithm);
|
||||
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 = new NodeCryptoFunctionService();
|
||||
const key = await cryptoFunctionService.hkdf(
|
||||
ikm.buffer,
|
||||
ikm,
|
||||
Utils.fromUtf8ToArray(regularSalt).buffer,
|
||||
Utils.fromUtf8ToArray(regularInfo).buffer,
|
||||
32,
|
||||
@@ -395,9 +392,8 @@ function testHkdfExpand(
|
||||
|
||||
it("should create valid " + algorithm + " " + outputByteSize + " byte okm", async () => {
|
||||
const cryptoFunctionService = new NodeCryptoFunctionService();
|
||||
const prk = Utils.fromB64ToArray(b64prk);
|
||||
const okm = await cryptoFunctionService.hkdfExpand(
|
||||
prk.buffer,
|
||||
Utils.fromB64ToArray(b64prk),
|
||||
info,
|
||||
outputByteSize,
|
||||
algorithm,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
import { StorageService } from "@/jslib/common/src/abstractions/storage.service";
|
||||
import { NodeUtils } from "@/jslib/common/src/misc/nodeUtils";
|
||||
import { sequentialize } from "@/jslib/common/src/misc/sequentialize";
|
||||
import Utils from "@/jslib/common/src/misc/utils";
|
||||
import { Utils } from "@/jslib/common/src/misc/utils";
|
||||
|
||||
export class LowdbStorageService implements StorageService {
|
||||
protected dataFilePath: string;
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as crypto from "crypto";
|
||||
import * as forge from "node-forge";
|
||||
|
||||
import { CryptoFunctionService } from "@/jslib/common/src/abstractions/cryptoFunction.service";
|
||||
import Utils from "@/jslib/common/src/misc/utils";
|
||||
import { Utils } from "@/jslib/common/src/misc/utils";
|
||||
import { DecryptParameters } from "@/jslib/common/src/models/domain/decryptParameters";
|
||||
import { SymmetricCryptoKey } from "@/jslib/common/src/models/domain/symmetricCryptoKey";
|
||||
|
||||
@@ -147,22 +147,19 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
|
||||
): DecryptParameters<ArrayBuffer> {
|
||||
const p = new DecryptParameters<ArrayBuffer>();
|
||||
p.encKey = key.encKey;
|
||||
const dataArr = Utils.fromB64ToArray(data);
|
||||
p.data = dataArr.buffer.slice(dataArr.byteOffset, dataArr.byteOffset + dataArr.byteLength) as ArrayBuffer;
|
||||
const ivArr = Utils.fromB64ToArray(iv);
|
||||
p.iv = ivArr.buffer.slice(ivArr.byteOffset, ivArr.byteOffset + ivArr.byteLength) as ArrayBuffer;
|
||||
p.data = Utils.fromB64ToArray(data).buffer;
|
||||
p.iv = Utils.fromB64ToArray(iv).buffer;
|
||||
|
||||
const macData = new Uint8Array(p.iv.byteLength + p.data.byteLength);
|
||||
macData.set(new Uint8Array(p.iv), 0);
|
||||
macData.set(new Uint8Array(p.data), p.iv.byteLength);
|
||||
p.macData = macData.buffer.slice(macData.byteOffset, macData.byteOffset + macData.byteLength) as ArrayBuffer;
|
||||
p.macData = macData.buffer;
|
||||
|
||||
if (key.macKey != null) {
|
||||
p.macKey = key.macKey;
|
||||
}
|
||||
if (mac != null) {
|
||||
const macArr = Utils.fromB64ToArray(mac);
|
||||
p.mac = macArr.buffer.slice(macArr.byteOffset, macArr.byteOffset + macArr.byteLength) as ArrayBuffer;
|
||||
p.mac = Utils.fromB64ToArray(mac).buffer;
|
||||
}
|
||||
|
||||
return p;
|
||||
@@ -218,8 +215,7 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
|
||||
const publicKeyAsn1 = forge.pki.publicKeyToAsn1(forgePublicKey);
|
||||
const publicKeyByteString = forge.asn1.toDer(publicKeyAsn1).data;
|
||||
const publicKeyArray = Utils.fromByteStringToArray(publicKeyByteString);
|
||||
|
||||
return Promise.resolve(publicKeyArray.buffer as ArrayBuffer);
|
||||
return Promise.resolve(publicKeyArray.buffer);
|
||||
}
|
||||
|
||||
async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[ArrayBuffer, ArrayBuffer]> {
|
||||
@@ -245,7 +241,7 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
|
||||
const privateKeyByteString = forge.asn1.toDer(privateKeyPkcs8).getBytes();
|
||||
const privateKey = Utils.fromByteStringToArray(privateKeyByteString);
|
||||
|
||||
resolve([publicKey.buffer as ArrayBuffer, privateKey.buffer as ArrayBuffer]);
|
||||
resolve([publicKey.buffer, privateKey.buffer]);
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -280,12 +276,9 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
|
||||
private toArrayBuffer(value: Buffer | string | ArrayBuffer): ArrayBuffer {
|
||||
let buf: ArrayBuffer;
|
||||
if (typeof value === "string") {
|
||||
const arr = Utils.fromUtf8ToArray(value);
|
||||
buf = arr.buffer.slice(arr.byteOffset, arr.byteOffset + arr.byteLength) as ArrayBuffer;
|
||||
} else if (Buffer.isBuffer(value)) {
|
||||
buf = value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength) as ArrayBuffer;
|
||||
buf = Utils.fromUtf8ToArray(value).buffer;
|
||||
} else {
|
||||
buf = value;
|
||||
buf = new Uint8Array(value).buffer;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
1069
package-lock.json
generated
1069
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
37
package.json
37
package.json
@@ -2,7 +2,7 @@
|
||||
"name": "@bitwarden/directory-connector",
|
||||
"productName": "Bitwarden Directory Connector",
|
||||
"description": "Sync your user directory to your Bitwarden organization.",
|
||||
"version": "2025.11.0",
|
||||
"version": "2025.12.0",
|
||||
"keywords": [
|
||||
"bitwarden",
|
||||
"password",
|
||||
@@ -31,14 +31,14 @@
|
||||
"lint": "eslint . && prettier --check .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"build": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\"",
|
||||
"build:main": "webpack --config webpack.main.js",
|
||||
"build:renderer": "webpack --config webpack.renderer.js",
|
||||
"build:renderer:watch": "webpack --config webpack.renderer.js --watch",
|
||||
"build:main": "webpack --config webpack.main.cjs",
|
||||
"build:renderer": "webpack --config webpack.renderer.cjs",
|
||||
"build:renderer:watch": "webpack --config webpack.renderer.cjs --watch",
|
||||
"build:dist": "npm run reset && npm run rebuild && npm run build",
|
||||
"build:cli": "webpack --config webpack.cli.js",
|
||||
"build:cli:watch": "webpack --config webpack.cli.js --watch",
|
||||
"build:cli:prod": "cross-env NODE_ENV=production webpack --config webpack.cli.js",
|
||||
"build:cli:prod:watch": "cross-env NODE_ENV=production webpack --config webpack.cli.js --watch",
|
||||
"build:cli": "webpack --config webpack.cli.cjs",
|
||||
"build:cli:watch": "webpack --config webpack.cli.cjs --watch",
|
||||
"build:cli:prod": "cross-env NODE_ENV=production webpack --config webpack.cli.cjs",
|
||||
"build:cli:prod:watch": "cross-env NODE_ENV=production webpack --config webpack.cli.cjs --watch",
|
||||
"electron": "npm run build:main && concurrently -k -n Main,Rend -c yellow,cyan \"electron --inspect=5858 ./build --watch\" \"npm run build:renderer:watch\"",
|
||||
"electron:ignore": "npm run build:main && concurrently -k -n Main,Rend -c yellow,cyan \"electron --inspect=5858 --ignore-certificate-errors ./build --watch\" \"npm run build:renderer:watch\"",
|
||||
"clean:dist": "rimraf --glob ./dist/*",
|
||||
@@ -74,8 +74,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "20.3.3",
|
||||
"@angular-eslint/eslint-plugin-template": "20.6.0",
|
||||
"@angular-eslint/template-parser": "20.6.0",
|
||||
"@angular-eslint/eslint-plugin-template": "20.7.0",
|
||||
"@angular-eslint/template-parser": "20.7.0",
|
||||
"@angular/compiler-cli": "20.3.15",
|
||||
"@electron/notarize": "2.5.0",
|
||||
"@electron/rebuild": "4.0.1",
|
||||
@@ -85,10 +85,11 @@
|
||||
"@types/inquirer": "8.2.10",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/lowdb": "1.0.15",
|
||||
"@types/node": "22.18.1",
|
||||
"@types/node": "22.19.2",
|
||||
"@types/node-fetch": "2.6.12",
|
||||
"@types/node-forge": "1.3.11",
|
||||
"@types/proper-lockfile": "4.1.4",
|
||||
"@types/semver": "7.7.1",
|
||||
"@types/tldjs": "2.3.4",
|
||||
"@typescript-eslint/eslint-plugin": "8.48.0",
|
||||
"@typescript-eslint/parser": "8.48.0",
|
||||
@@ -105,21 +106,21 @@
|
||||
"electron-reload": "2.0.0-alpha.1",
|
||||
"electron-store": "8.2.0",
|
||||
"electron-updater": "6.6.2",
|
||||
"eslint": "8.57.1",
|
||||
"eslint": "9.39.1",
|
||||
"eslint-config-prettier": "10.1.5",
|
||||
"eslint-import-resolver-typescript": "4.4.4",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-rxjs": "5.0.3",
|
||||
"eslint-plugin-rxjs-angular": "2.0.1",
|
||||
"eslint-plugin-rxjs-angular-x": "0.1.0",
|
||||
"eslint-plugin-rxjs-x": "0.8.3",
|
||||
"form-data": "4.0.4",
|
||||
"glob": "11.1.0",
|
||||
"glob": "13.0.0",
|
||||
"html-loader": "5.1.0",
|
||||
"html-webpack-plugin": "5.6.3",
|
||||
"husky": "9.1.7",
|
||||
"jest": "29.7.0",
|
||||
"jest-junit": "16.0.0",
|
||||
"jest-mock-extended": "3.0.7",
|
||||
"jest-preset-angular": "14.6.0",
|
||||
"jest-mock-extended": "4.0.0",
|
||||
"jest-preset-angular": "16.0.0",
|
||||
"lint-staged": "16.2.6",
|
||||
"mini-css-extract-plugin": "2.9.2",
|
||||
"minimatch": "5.1.2",
|
||||
@@ -134,7 +135,7 @@
|
||||
"ts-loader": "9.5.2",
|
||||
"tsconfig-paths-webpack-plugin": "4.2.0",
|
||||
"type-fest": "5.3.0",
|
||||
"typescript": "5.9.3",
|
||||
"typescript": "5.8.3",
|
||||
"webpack": "5.103.0",
|
||||
"webpack-cli": "6.0.1",
|
||||
"webpack-merge": "6.0.1",
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { notarize } from "@electron/notarize";
|
||||
import { config } from "dotenv";
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
require("dotenv").config();
|
||||
const { notarize } = require("@electron/notarize");
|
||||
|
||||
config();
|
||||
|
||||
export default async function notarizing(context) {
|
||||
exports.default = async function notarizing(context) {
|
||||
const { electronPlatformName, appOutDir } = context;
|
||||
if (electronPlatformName !== "darwin") {
|
||||
return;
|
||||
@@ -34,4 +33,4 @@ export default async function notarizing(context) {
|
||||
appleIdPassword: appleIdPassword,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
/* eslint-disable no-console */
|
||||
import { execSync } from "child_process";
|
||||
|
||||
export default async function (configuration) {
|
||||
if (
|
||||
parseInt(process.env.ELECTRON_BUILDER_SIGN) === 1 &&
|
||||
configuration.path.slice(-4) === ".exe"
|
||||
) {
|
||||
/* eslint-disable @typescript-eslint/no-var-requires, no-console */
|
||||
exports.default = async function (configuration) {
|
||||
if (parseInt(process.env.ELECTRON_BUILDER_SIGN) === 1 && configuration.path.slice(-4) == ".exe") {
|
||||
console.log(`[*] Signing file: ${configuration.path}`);
|
||||
execSync(
|
||||
require("child_process").execSync(
|
||||
`azuresigntool sign ` +
|
||||
`-kvu ${process.env.SIGNING_VAULT_URL} ` +
|
||||
`-kvi ${process.env.SIGNING_CLIENT_ID} ` +
|
||||
@@ -23,4 +18,4 @@ export default async function (configuration) {
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ModalService } from "@/jslib/angular/src/services/modal.service";
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@/jslib/common/src/abstractions/platformUtils.service";
|
||||
import Utils from "@/jslib/common/src/misc/utils";
|
||||
import { Utils } from "@/jslib/common/src/misc/utils";
|
||||
|
||||
import { AuthService } from "../../abstractions/auth.service";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
@@ -23,7 +23,7 @@ import { EnvironmentComponent } from "./environment.component";
|
||||
// The only subscription in this component is closed from a child component, confusing eslint.
|
||||
// https://github.com/cartant/eslint-plugin-rxjs-angular/blob/main/docs/rules/prefer-takeuntil.md
|
||||
//
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
// eslint-disable-next-line rxjs-angular-x/prefer-takeuntil
|
||||
export class ApiKeyComponent {
|
||||
@ViewChild("environment", { read: ViewContainerRef, static: true })
|
||||
environmentModal: ViewContainerRef;
|
||||
@@ -100,7 +100,7 @@ export class ApiKeyComponent {
|
||||
this.environmentModal,
|
||||
);
|
||||
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
// eslint-disable-next-line rxjs-angular-x/prefer-takeuntil
|
||||
childComponent.onSaved.pipe(takeUntil(modalRef.onClosed)).subscribe(() => {
|
||||
modalRef.close();
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as inquirer from "inquirer";
|
||||
import { Response } from "@/jslib/node/src/cli/models/response";
|
||||
import { MessageResponse } from "@/jslib/node/src/cli/models/response/messageResponse";
|
||||
|
||||
import Utils from "../../jslib/common/src/misc/utils";
|
||||
import { Utils } from "../../jslib/common/src/misc/utils";
|
||||
import { AuthService } from "../abstractions/auth.service";
|
||||
|
||||
export class LoginCommand {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { MenuMain } from "./menu.main";
|
||||
const SyncCheckInterval = 60 * 1000; // 1 minute
|
||||
|
||||
export class MessagingMain {
|
||||
private syncTimeout: NodeJS.Timeout;
|
||||
private syncTimeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
constructor(
|
||||
private windowMain: WindowMain,
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as path from "path";
|
||||
import * as chalk from "chalk";
|
||||
import { Command, OptionValues } from "commander";
|
||||
|
||||
import Utils from "@/jslib/common/src/misc/utils";
|
||||
import { Utils } from "@/jslib/common/src/misc/utils";
|
||||
import { BaseProgram } from "@/jslib/node/src/cli/baseProgram";
|
||||
import { UpdateCommand } from "@/jslib/node/src/cli/commands/update.command";
|
||||
import { Response } from "@/jslib/node/src/cli/models/response";
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
import { ApiService } from "@/jslib/common/src/abstractions/api.service";
|
||||
import { AppIdService } from "@/jslib/common/src/abstractions/appId.service";
|
||||
import { PlatformUtilsService } from "@/jslib/common/src/abstractions/platformUtils.service";
|
||||
import Utils from "@/jslib/common/src/misc/utils";
|
||||
import { Utils } from "@/jslib/common/src/misc/utils";
|
||||
import {
|
||||
AccountKeys,
|
||||
AccountProfile,
|
||||
|
||||
@@ -132,7 +132,7 @@ export class EntraIdDirectoryService extends BaseDirectoryService implements IDi
|
||||
}
|
||||
|
||||
const setFilter = this.createCustomUserSet(this.syncConfig.userFilter);
|
||||
// eslint-disable-next-line
|
||||
|
||||
while (true) {
|
||||
const users: graphType.User[] = res.value;
|
||||
if (users != null) {
|
||||
@@ -211,7 +211,7 @@ export class EntraIdDirectoryService extends BaseDirectoryService implements IDi
|
||||
let auMembers = await this.client
|
||||
.api(`${this.getGraphApiEndpoint()}/v1.0/directory/administrativeUnits/${p}/members`)
|
||||
.get();
|
||||
// eslint-disable-next-line
|
||||
|
||||
while (true) {
|
||||
for (const auMember of auMembers.value) {
|
||||
const groupId = auMember.id;
|
||||
@@ -328,7 +328,7 @@ export class EntraIdDirectoryService extends BaseDirectoryService implements IDi
|
||||
const entries: GroupEntry[] = [];
|
||||
const groupsReq = this.client.api("/groups");
|
||||
let res = await groupsReq.get();
|
||||
// eslint-disable-next-line
|
||||
|
||||
while (true) {
|
||||
const groups: graphType.Group[] = res.value;
|
||||
if (groups != null) {
|
||||
@@ -421,7 +421,7 @@ export class EntraIdDirectoryService extends BaseDirectoryService implements IDi
|
||||
|
||||
const memReq = this.client.api("/groups/" + group.id + "/members");
|
||||
let memRes = await memReq.get();
|
||||
// eslint-disable-next-line
|
||||
|
||||
while (true) {
|
||||
const members: any = memRes.value;
|
||||
if (members != null) {
|
||||
|
||||
@@ -71,7 +71,7 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
|
||||
let nextPageToken: string = null;
|
||||
|
||||
const filter = this.createCustomSet(this.syncConfig.userFilter);
|
||||
// eslint-disable-next-line
|
||||
|
||||
while (true) {
|
||||
this.logService.info("Querying users - nextPageToken:" + nextPageToken);
|
||||
const p = Object.assign({ query: query, pageToken: nextPageToken }, this.authParams);
|
||||
@@ -99,7 +99,7 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
|
||||
}
|
||||
|
||||
nextPageToken = null;
|
||||
// eslint-disable-next-line
|
||||
|
||||
while (true) {
|
||||
this.logService.info("Querying deleted users - nextPageToken:" + nextPageToken);
|
||||
const p = Object.assign(
|
||||
@@ -154,7 +154,6 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
|
||||
const query = this.createDirectoryQuery(this.syncConfig.groupFilter);
|
||||
let nextPageToken: string = null;
|
||||
|
||||
// eslint-disable-next-line
|
||||
while (true) {
|
||||
this.logService.info("Querying groups - nextPageToken:" + nextPageToken);
|
||||
let p = null;
|
||||
@@ -194,7 +193,6 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
|
||||
entry.externalId = group.id;
|
||||
entry.name = group.name;
|
||||
|
||||
// eslint-disable-next-line
|
||||
while (true) {
|
||||
const p = Object.assign({ groupKey: group.id, pageToken: nextPageToken }, this.authParams);
|
||||
const memRes = await this.service.members.list(p);
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as ldapts from "ldapts";
|
||||
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
import Utils from "@/jslib/common/src/misc/utils";
|
||||
import { Utils } from "@/jslib/common/src/misc/utils";
|
||||
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { DirectoryType } from "../../enums/directoryType";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as lock from "proper-lockfile";
|
||||
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
import Utils from "@/jslib/common/src/misc/utils";
|
||||
import { Utils } from "@/jslib/common/src/misc/utils";
|
||||
import { LowdbStorageService as LowdbStorageServiceBase } from "@/jslib/node/src/services/lowdbStorage.service";
|
||||
|
||||
export class LowdbStorageService extends LowdbStorageServiceBase {
|
||||
|
||||
@@ -116,6 +116,7 @@ describe("SyncService", () => {
|
||||
stateService.getLastSyncHash.mockResolvedValue("unique hash");
|
||||
|
||||
// @ts-expect-error This is a workaround to make the batchsize smaller to trigger the batching logic since its a const.
|
||||
// eslint-disable-next-line no-import-assign
|
||||
constants.batchSize = 4;
|
||||
|
||||
const syncResult = await syncService.sync(false, false);
|
||||
@@ -130,6 +131,7 @@ describe("SyncService", () => {
|
||||
expect(apiService.postPublicImportDirectory).toHaveBeenCalledTimes(7);
|
||||
|
||||
// @ts-expect-error Reset batch size to original state.
|
||||
// eslint-disable-next-line no-import-assign
|
||||
constants.batchSize = originalBatchSize;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -97,6 +97,7 @@ describe("SyncService", () => {
|
||||
stateService.getLastSyncHash.mockResolvedValue("unique hash");
|
||||
|
||||
// @ts-expect-error This is a workaround to make the batchsize smaller to trigger the batching logic since its a const.
|
||||
// eslint-disable-next-line no-import-assign
|
||||
constants.batchSize = 4;
|
||||
|
||||
const mockRequests = new Array(6).fill({
|
||||
@@ -119,6 +120,7 @@ describe("SyncService", () => {
|
||||
expect(apiService.postPublicImportDirectory).toHaveBeenCalledWith(mockRequests[5]);
|
||||
|
||||
// @ts-expect-error Reset batch size back to original value.
|
||||
// eslint-disable-next-line no-import-assign
|
||||
constants.batchSize = originalBatchSize;
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { CryptoFunctionService } from "@/jslib/common/src/abstractions/cryptoFun
|
||||
import { EnvironmentService } from "@/jslib/common/src/abstractions/environment.service";
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
import { MessagingService } from "@/jslib/common/src/abstractions/messaging.service";
|
||||
import Utils from "@/jslib/common/src/misc/utils";
|
||||
import { Utils } from "@/jslib/common/src/misc/utils";
|
||||
import { OrganizationImportRequest } from "@/jslib/common/src/models/request/organizationImportRequest";
|
||||
|
||||
import { DirectoryFactoryService } from "../abstractions/directory-factory.service";
|
||||
|
||||
Reference in New Issue
Block a user