1
0
mirror of https://github.com/bitwarden/directory-connector synced 2025-12-05 23:53:21 +00:00

Compare commits

..

45 Commits

Author SHA1 Message Date
renovate[bot]
752132f0b0 [deps]: Lock file maintenance (#731)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 40a85bb875)
2025-03-24 17:02:06 +00:00
Vincent Salucci
50be1218e2 chore: version bump to v2025.3.0 (#733) 2025-03-24 10:02:14 -05:00
Matt Andreko
e4abb2c751 Update SARIF upload to use proper branch (#732) 2025-03-21 09:12:47 -04:00
Hendrik Heil
23c591f903 feat: disable archived google users (#715)
Google Workspace users in an "archived" state will be treated
as a disabled user by Directory Connector. e.g. they will be removed
from the organization if "remove disabled users during sync" is enabled.
2025-03-19 15:18:31 +10:00
Brandon Treston
2ea2fd701c [PM-14360] Import Batching (#703)
* initial implementation

* next iteration

* clean up

* cleanup

* fix test

* cleanup

* rename services, refactor, fix test logic

* fix DI

* update comment

* update comment

* resolve merge conflict

* register dependencies out of jslib, more cleanup

* fix failing test in build

* fix type error

* try fixing test error

* remove async from test setup

* attempt to fix test

* fix text

* fix test

* add ts-expect-error comment

* cleanup

---------

Co-authored-by: bnagawiecki <107435978+bnagawiecki@users.noreply.github.com>
2025-03-10 14:54:00 -04:00
bnagawiecki
3b74be446e add example ldif file with 11k users (#724) 2025-03-10 14:44:10 -04:00
renovate[bot]
2651a53f27 [deps]: Update googleapis to v144 (#619)
* [deps]: Update googleapis to v144

* [deps]: Update google-auth-library to v9

* Group Google libraries in Renovate configuration

* Reinstall google libraries to fix missing gaxios package

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Rui Tome <rtome@bitwarden.com>
2025-03-07 10:46:17 +00:00
Matt Andreko
09ed8326c3 Replace secret checking logic with branch detection logic (#726) 2025-03-03 11:34:11 -05:00
Thomas Rittson
c5a65a85ad Fix mkcert bash script (#723)
Wrap CAROOT path in quotes to handle spaces in path
2025-02-20 10:00:42 +10:00
renovate[bot]
3ae90cbb4c [deps]: Lock file maintenance (#565)
* [deps]: Lock file maintenance

* Add minimatch@3.1.2 to package dependencies

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Rui Tome <rtome@bitwarden.com>
2025-02-19 11:34:28 +00:00
Thomas Rittson
99dbb3162e Remove typescript-transform-paths dependency (#720)
No longer needed - replaced by tsconfig-paths-webpack-plugin
2025-02-17 10:02:19 +10:00
renovate[bot]
f146d41b66 [deps]: Update electron to v34 (#713)
* [deps]: Update electron to v34

* fix: remove event parameter from minimize handler for Electron 34 compatibility

The minimize event in Electron 34 no longer provides an event object, requiring removal of the e.preventDefault() call.

* feat(deps): add node-abi@3.74.0 as devDependency for Electron 34 rebuild

Required for electron-rebuild to work correctly with Electron 34.
Previous transitive dependency version (3.68.0) was too old to support Electron 34.

* Bump Electron to v34.1.1

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Rui Tome <rtome@bitwarden.com>
2025-02-14 11:32:49 +00:00
Thomas Rittson
b35cf8e995 Regenerate package-lock.json (#722) 2025-02-14 10:15:26 +00:00
renovate[bot]
f7ee5dcd92 [deps]: Update webpack-cli to v6 (#702)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-13 17:00:14 +00:00
renovate[bot]
61bbff771e [deps]: Update Node.js to v22 (#671)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-13 07:56:13 -06:00
renovate[bot]
2047b6644e [deps]: Update core-js to v3.40.0 (#669)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-12 15:50:52 -06:00
renovate[bot]
26dd9662cf [deps]: Update commander to v13 (#705)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-12 15:09:52 -06:00
renovate[bot]
70073fb570 [deps]: Update typescript-eslint monorepo to v8.23.0 (#712)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-12 14:42:45 -06:00
renovate[bot]
8642b9d7aa [deps]: Update concurrently to v9.1.2 (#704)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-12 14:23:02 -06:00
renovate[bot]
d77b50c540 [deps]: Update webpack to v5.97.1 (#670)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-06 16:22:23 +00:00
Matt Andreko
ed935d998a Modified Scan workflow to contain PR number during a PR (#718) 2025-02-04 15:25:47 -05:00
renovate[bot]
682da52040 [deps]: Update ts-loader to v9.5.2 (#710)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-30 13:25:37 -05:00
renovate[bot]
531619af1d [deps]: Update eslint-config-prettier to v10 (#714)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-30 13:19:51 -05:00
renovate[bot]
cf54858cc5 [deps]: Update ngx-toastr to v19 (#689)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-23 10:16:54 -06:00
renovate[bot]
6cc022b135 [deps]: Update jest-preset-angular to v14.5.0 (#711)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-23 09:53:02 -06:00
renovate[bot]
a8a4390624 [deps]: Update eslint-import-resolver-typescript to v3.7.0 (#686)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-23 09:52:01 -06:00
renovate[bot]
f9d817f0b1 [deps]: Update gh minor (#645)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-23 15:38:39 +00:00
github-actions
112bda1137 Bumped version to 2025.1.1 2025-01-23 13:42:01 +00:00
renovate[bot]
23713d92fa [deps]: Update type-fest to v4.32.0 (#696)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-17 10:57:42 +10:00
renovate[bot]
6ebc9631aa [deps]: Update lint-staged to v15.4.1 (#695)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-17 10:44:56 +10:00
Matt Andreko
e8579f11d3 Removed unnecessary CODECOV_TOKEN with updated codecov-action (#709) 2025-01-16 08:53:26 -05:00
renovate[bot]
6b2c7a5f00 [deps]: Update ldapts to v7.3.1 (#699)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-16 17:19:22 +10:00
renovate[bot]
2a1a5bf064 [deps]: Update @types/node to v20.17.14 (#694)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-16 17:15:42 +10:00
Vince Grassia
1464d72b27 BRE-549 - Add rc branch logic (#708) 2025-01-14 22:24:48 -06:00
Vincent Salucci
f5cbd8f03d chore: version bump to 2025.1.0 (#707) 2025-01-14 13:16:09 -06:00
Matt Andreko
fdbbef68c1 Update SonarQube GitHub Action (#706)
* Updated SonarQube GitHub action to v4.2.1

* Removed unnecessary github token
2025-01-14 09:28:46 -05:00
renovate[bot]
efb412684d [deps]: Update rimraf to v6 (#690)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-10 12:20:13 -05:00
renovate[bot]
79f7a2b495 [deps]: Update typescript-eslint monorepo to v8 (#563)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-03 11:57:59 -06:00
renovate[bot]
4342734412 [deps]: Update https-proxy-agent to v7.0.6 (#684)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-27 15:44:26 -05:00
renovate[bot]
62f14e5043 [deps]: Update prettier to v3.4.2 (#687)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-27 13:47:47 -05:00
renovate[bot]
c2b22518fe [deps]: Update codecov/codecov-action action to v5 (#681)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-20 16:38:11 -05:00
Thomas Rittson
37c992f16b [PM-13007] Fix Active Directory externalId parsing (#693)
Return AD ObjectGuid attribute as buffer so it can be parsed properly
2024-12-19 07:43:47 +10:00
renovate[bot]
69156677ac [deps]: Update electron to v33 - abandoned (#658)
* [deps]: Update electron to v33

* remove event parameter from handler function params

* change to v.32.1.1

* clean up

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Brandon <btreston@bitwarden.com>
2024-12-17 12:40:34 -05:00
renovate[bot]
aaed7b13ea [deps]: Update ldapts to v7.2.2 (#685)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Brandon Treston <btreston@bitwarden.com>
2024-12-17 11:46:10 -05:00
renovate[bot]
096d2a03ab [deps]: Update dotenv to v16.4.7 (#683)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-10 14:25:09 -05:00
29 changed files with 345076 additions and 4641 deletions

View File

@@ -7,6 +7,12 @@
"groupName": "gh minor",
"matchManagers": ["github-actions"],
"matchUpdateTypes": ["minor", "patch"]
},
{
"groupName": "Google Libraries",
"matchPackagePatterns": ["google-auth-library", "googleapis"],
"matchManagers": ["npm"],
"groupSlug": "google-libraries"
}
]
}

View File

@@ -5,6 +5,8 @@ on:
push:
branches:
- "main"
- "rc"
- "hotfix-rc"
workflow_dispatch: {}
jobs:
@@ -13,7 +15,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout repo
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up CLOC
run: |
@@ -31,7 +33,7 @@ jobs:
package_version: ${{ steps.retrieve-version.outputs.package_version }}
steps:
- name: Checkout repo
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Get Package Version
id: retrieve-version
@@ -50,10 +52,10 @@ jobs:
_PKG_FETCH_VERSION: 3.4
steps:
- name: Checkout repo
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -121,14 +123,14 @@ jobs:
fi
- name: Upload Linux Zip to GitHub
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: bwdc-linux-${{ env._PACKAGE_VERSION }}.zip
path: ./dist-cli/bwdc-linux-${{ env._PACKAGE_VERSION }}.zip
if-no-files-found: error
- name: Upload Linux checksum to GitHub
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: bwdc-linux-sha256-${{ env._PACKAGE_VERSION }}.txt
path: ./dist-cli/bwdc-linux-sha256-${{ env._PACKAGE_VERSION }}.txt
@@ -145,10 +147,10 @@ jobs:
_PKG_FETCH_VERSION: 3.4
steps:
- name: Checkout repo
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -209,14 +211,14 @@ jobs:
fi
- name: Upload Mac Zip to GitHub
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: bwdc-macos-${{ env._PACKAGE_VERSION }}.zip
path: ./dist-cli/bwdc-macos-${{ env._PACKAGE_VERSION }}.zip
if-no-files-found: error
- name: Upload Mac checksum to GitHub
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: bwdc-macos-sha256-${{ env._PACKAGE_VERSION }}.txt
path: ./dist-cli/bwdc-macos-sha256-${{ env._PACKAGE_VERSION }}.txt
@@ -232,7 +234,7 @@ jobs:
_WIN_PKG_VERSION: 3.4
steps:
- name: Checkout repo
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Windows builder
run: |
@@ -240,7 +242,7 @@ jobs:
choco install reshack --no-progress
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -353,14 +355,14 @@ jobs:
-t sha256 | Out-File ./dist-cli/bwdc-windows-sha256-${env:_PACKAGE_VERSION}.txt
- name: Upload Windows Zip to GitHub
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: bwdc-windows-${{ env._PACKAGE_VERSION }}.zip
path: ./dist-cli/bwdc-windows-${{ env._PACKAGE_VERSION }}.zip
if-no-files-found: error
- name: Upload Windows checksum to GitHub
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: bwdc-windows-sha256-${{ env._PACKAGE_VERSION }}.txt
path: ./dist-cli/bwdc-windows-sha256-${{ env._PACKAGE_VERSION }}.txt
@@ -377,10 +379,10 @@ jobs:
HUSKY: 0
steps:
- name: Checkout repo
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -413,28 +415,28 @@ jobs:
SIGNING_CERT_NAME: ${{ secrets.SIGNING_CERT_NAME }}
- name: Upload Portable Executable to GitHub
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: Bitwarden-Connector-Portable-${{ env._PACKAGE_VERSION }}.exe
path: ./dist/Bitwarden-Connector-Portable-${{ env._PACKAGE_VERSION }}.exe
if-no-files-found: error
- name: Upload Installer Executable to GitHub
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe
path: ./dist/Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe
if-no-files-found: error
- name: Upload Installer Executable Blockmap to GitHub
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe.blockmap
path: ./dist/Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe.blockmap
if-no-files-found: error
- name: Upload latest auto-update artifact
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: latest.yml
path: ./dist/latest.yml
@@ -451,10 +453,10 @@ jobs:
HUSKY: 0
steps:
- name: Checkout repo
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -481,14 +483,14 @@ jobs:
run: npm run dist:lin
- name: Upload AppImage
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-x86_64.AppImage
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-x86_64.AppImage
if-no-files-found: error
- name: Upload latest auto-update artifact
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: latest-linux.yml
path: ./dist/latest-linux.yml
@@ -505,10 +507,10 @@ jobs:
HUSKY: 0
steps:
- name: Checkout repo
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -575,7 +577,7 @@ jobs:
- name: Install Node dependencies
run: npm install
- name: Set up private auth key
run: |
mkdir ~/private_keys
@@ -592,28 +594,28 @@ jobs:
CSC_FOR_PULL_REQUEST: true
- name: Upload .zip artifact
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-mac.zip
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-mac.zip
if-no-files-found: error
- name: Upload .dmg artifact
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg
if-no-files-found: error
- name: Upload .dmg Blockmap artifact
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg.blockmap
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg.blockmap
if-no-files-found: error
- name: Upload latest auto-update artifact
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: latest-mac.yml
path: ./dist/latest-mac.yml
@@ -634,7 +636,11 @@ jobs:
- macos-gui
steps:
- name: Check if any job failed
if: github.ref == 'refs/heads/main' && contains(needs.*.result, 'failure')
if: |
(github.ref == 'refs/heads/main'
|| github.ref == 'refs/heads/rc'
|| github.ref == 'refs/heads/hotfix-rc')
&& contains(needs.*.result, 'failure')
run: exit 1
- name: Login to Azure - CI subscription

View File

@@ -8,39 +8,21 @@ on:
paths:
- ".github/workflows/integration-test.yml" # this file
- "src/services/ldap-directory.service*" # we only have integration for LDAP testing at the moment
- "./openldap*" # any change to test fixtures
- "./openldap/**/*" # any change to test fixtures
- "./docker-compose.yml" # any change to Docker configuration
pull_request:
paths:
- ".github/workflows/integration-test.yml" # this file
- "src/services/ldap-directory.service*" # we only have integration for LDAP testing at the moment
- "./openldap*" # any change to test fixtures
- "./openldap/**/*" # any change to test fixtures
- "./docker-compose.yml" # any change to Docker configuration
jobs:
check-test-secrets:
name: Check for test secrets
runs-on: ubuntu-22.04
outputs:
available: ${{ steps.check-test-secrets.outputs.available }}
permissions:
contents: read
steps:
- name: Check
id: check-test-secrets
run: |
if [ "${{ secrets.CODECOV_TOKEN }}" != '' ]; then
echo "available=true" >> $GITHUB_OUTPUT;
else
echo "available=false" >> $GITHUB_OUTPUT;
fi
testing:
name: Run tests
if: ${{ startsWith(github.head_ref, 'version_bump_') == false }}
runs-on: ubuntu-22.04
needs: check-test-secrets
permissions:
checks: write
contents: read
@@ -48,7 +30,7 @@ jobs:
steps:
- name: Check out repo
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Get Node version
id: retrieve-node-version
@@ -58,7 +40,7 @@ jobs:
echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT
- name: Set up Node
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -80,7 +62,7 @@ jobs:
- name: Report test results
uses: dorny/test-reporter@31a54ee7ebcacc03a09ea97a7e5465a47b84aea5 # v1.9.1
if: ${{ needs.check-test-secrets.outputs.available == 'true' && !cancelled() }}
if: ${{ github.event.pull_request.head.repo.full_name == github.repository && !cancelled() }}
with:
name: Test Results
path: "junit.xml"
@@ -88,13 +70,9 @@ jobs:
fail-on-error: true
- name: Upload coverage to codecov.io
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0
if: ${{ needs.check-test-secrets.outputs.available == 'true' }}
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
uses: codecov/codecov-action@5a605bd92782ce0810fa3b8acc235c921b497052 # v5.2.0
- name: Upload results to codecov.io
uses: codecov/test-results-action@1b5b448b98e58ba90d1a1a1d9fcb72ca2263be46 # v1.0.0
if: ${{ needs.check-test-secrets.outputs.available == 'true' }}
uses: codecov/test-results-action@4e79e65778be1cecd5df25e14af1eafb6df80ea9 # v1.0.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -18,17 +18,17 @@ jobs:
name: Setup
runs-on: ubuntu-24.04
outputs:
release-version: ${{ steps.version.outputs.version }}
release_version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout repo
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Branch check
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
if: ${{ inputs.release_type != 'Dry Run' }}
run: |
if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then
echo "==================================="
echo "[!] Can only release from the 'main' branch"
echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches"
echo "==================================="
exit 1
fi
@@ -37,7 +37,7 @@ jobs:
id: version
uses: bitwarden/gh-actions/release-version-check@main
with:
release-type: ${{ github.event.inputs.release_type }}
release-type: ${{ inputs.release_type }}
project-type: ts
file: package.json
@@ -47,7 +47,7 @@ jobs:
needs: setup
steps:
- name: Download all artifacts
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
if: ${{ inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@main
with:
workflow: build.yml
@@ -55,7 +55,7 @@ jobs:
branch: ${{ github.ref_name }}
- name: Dry Run - Download all artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
if: ${{ inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@main
with:
workflow: build.yml
@@ -63,10 +63,10 @@ jobs:
branch: main
- name: Create release
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0
if: ${{ inputs.release_type != 'Dry Run' }}
uses: ncipollo/release-action@cdcc88a9acf3ca41c16c37bb7d21b9ad48560d87 # v1.15.0
env:
PKG_VERSION: ${{ needs.setup.outputs.release-version }}
PKG_VERSION: ${{ needs.setup.outputs.release_version }}
with:
artifacts: "./bwdc-windows-${{ env.PKG_VERSION }}.zip,
./bwdc-macos-${{ env.PKG_VERSION }}.zip,

View File

@@ -24,12 +24,12 @@ jobs:
steps:
- name: Check out repo
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Scan with Checkmarx
uses: checkmarx/ast-github-action@ed196cdaec9cd1bc5aacac4ca2010dd773b20893 # 2.0.35
uses: checkmarx/ast-github-action@184bf2f64f55d1c93fd6636d539edf274703e434 # 2.0.41
env:
INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}"
with:
@@ -44,9 +44,11 @@ jobs:
--output-path . ${{ env.INCREMENTAL }}
- name: Upload Checkmarx results to GitHub
uses: github/codeql-action/upload-sarif@e2b3eafc8d227b0241d48be5f425d47c2d750a13 # v3.26.10
uses: github/codeql-action/upload-sarif@dd196fa9ce80b6bacc74ca1c32bd5b0ba22efca7 # v3.28.3
with:
sarif_file: cx_result.sarif
sha: ${{ contains(github.event_name, 'pull_request') && github.event.pull_request.head.sha || github.sha }}
ref: ${{ contains(github.event_name, 'pull_request') && format('refs/pull/{0}/head', github.event.pull_request.number) || github.ref }}
quality:
name: Quality scan
@@ -58,16 +60,15 @@ jobs:
steps:
- name: Check out repo
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- name: Scan with SonarCloud
uses: sonarsource/sonarcloud-github-action@eb211723266fe8e83102bac7361f0a05c3ac1d1b # v3.0.0
uses: sonarsource/sonarqube-scan-action@bfd4e558cda28cda6b5defafb9232d191be8c203 # v4.2.1
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: >
-Dsonar.organization=${{ github.repository_owner }}
@@ -76,3 +77,4 @@ jobs:
-Dsonar.sources=.
-Dsonar.test.inclusions=**/*.spec.ts
-Dsonar.exclusions=**/*.spec.ts
-Dsonar.pullrequest.key=${{ github.event.pull_request.number }}

View File

@@ -5,32 +5,16 @@ on:
push:
branches:
- "main"
- "rc"
- "hotfix-rc"
pull_request:
jobs:
check-test-secrets:
name: Check for test secrets
runs-on: ubuntu-24.04
outputs:
available: ${{ steps.check-test-secrets.outputs.available }}
permissions:
contents: read
steps:
- name: Check
id: check-test-secrets
run: |
if [ "${{ secrets.CODECOV_TOKEN }}" != '' ]; then
echo "available=true" >> $GITHUB_OUTPUT;
else
echo "available=false" >> $GITHUB_OUTPUT;
fi
testing:
name: Run tests
if: ${{ startsWith(github.head_ref, 'version_bump_') == false }}
runs-on: ubuntu-24.04
needs: check-test-secrets
permissions:
checks: write
contents: read
@@ -38,7 +22,7 @@ jobs:
steps:
- name: Check out repo
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Get Node version
id: retrieve-node-version
@@ -48,7 +32,7 @@ jobs:
echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
@@ -68,7 +52,7 @@ jobs:
- name: Report test results
uses: dorny/test-reporter@31a54ee7ebcacc03a09ea97a7e5465a47b84aea5 # v1.9.1
if: ${{ needs.check-test-secrets.outputs.available == 'true' && !cancelled() }}
if: ${{ github.event.pull_request.head.repo.full_name == github.repository && !cancelled() }}
with:
name: Test Results
path: "junit.xml"
@@ -76,13 +60,9 @@ jobs:
fail-on-error: true
- name: Upload coverage to codecov.io
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0
if: ${{ needs.check-test-secrets.outputs.available == 'true' }}
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
uses: codecov/codecov-action@5a605bd92782ce0810fa3b8acc235c921b497052 # v5.2.0
- name: Upload results to codecov.io
uses: codecov/test-results-action@1b5b448b98e58ba90d1a1a1d9fcb72ca2263be46 # v1.0.0
if: ${{ needs.check-test-secrets.outputs.available == 'true' }}
uses: codecov/test-results-action@4e79e65778be1cecd5df25e14af1eafb6df80ea9 # v1.0.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -20,14 +20,14 @@ jobs:
version: ${{ inputs.version_number_override }}
- name: Generate GH App token
uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
id: app-token
with:
app-id: ${{ secrets.BW_GHAPP_ID }}
private-key: ${{ secrets.BW_GHAPP_KEY }}
- name: Checkout Branch
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
token: ${{ steps.app-token.outputs.token }}

View File

@@ -2,9 +2,9 @@ import { ApiTokenRequest } from "../models/request/identityToken/apiTokenRequest
import { PasswordTokenRequest } from "../models/request/identityToken/passwordTokenRequest";
import { SsoTokenRequest } from "../models/request/identityToken/ssoTokenRequest";
import { OrganizationImportRequest } from "../models/request/organizationImportRequest";
import { IdentityCaptchaResponse } from '../models/response/identityCaptchaResponse';
import { IdentityTokenResponse } from '../models/response/identityTokenResponse';
import { IdentityTwoFactorResponse } from '../models/response/identityTwoFactorResponse';
import { IdentityCaptchaResponse } from "../models/response/identityCaptchaResponse";
import { IdentityTokenResponse } from "../models/response/identityTokenResponse";
import { IdentityTwoFactorResponse } from "../models/response/identityTwoFactorResponse";
export abstract class ApiService {
postIdentityToken: (

View File

@@ -60,9 +60,8 @@ export class TrayMain {
}
setupWindowListeners(win: BrowserWindow) {
win.on("minimize", async (e: Event) => {
win.on("minimize", async () => {
if (await this.stateService.getEnableMinimizeToTray()) {
e.preventDefault();
this.hideToTray();
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -6,5 +6,5 @@ fi
mkcert -install
mkdir -p ./openldap/certs
cp $(mkcert -CAROOT)/rootCA.pem ./openldap/certs/rootCA.pem
cp "$(mkcert -CAROOT)/rootCA.pem" ./openldap/certs/rootCA.pem
mkcert -key-file ./openldap/certs/openldap-key.pem -cert-file ./openldap/certs/openldap.pem localhost openldap

8066
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "@bitwarden/directory-connector",
"productName": "Bitwarden Directory Connector",
"description": "Sync your user directory to your Bitwarden organization.",
"version": "2024.10.0",
"version": "2025.3.0",
"keywords": [
"bitwarden",
"password",
@@ -73,40 +73,40 @@
"test:types": "npx tsc --noEmit"
},
"devDependencies": {
"@angular-devkit/build-angular": "19.0.3",
"@angular-devkit/build-angular": "17.3.11",
"@angular-eslint/eslint-plugin-template": "17.5.3",
"@angular-eslint/template-parser": "17.5.3",
"@angular/compiler-cli": "19.0.3",
"@angular/compiler-cli": "17.3.12",
"@electron/notarize": "2.5.0",
"@electron/rebuild": "3.7.1",
"@fluffy-spoon/substitute": "1.208.0",
"@microsoft/microsoft-graph-types": "2.40.0",
"@ngtools/webpack": "19.0.0",
"@ngtools/webpack": "17.3.11",
"@types/inquirer": "8.2.10",
"@types/jest": "29.5.14",
"@types/lowdb": "1.0.15",
"@types/node": "20.17.9",
"@types/node": "22.13.1",
"@types/node-fetch": "2.6.12",
"@types/node-forge": "1.3.11",
"@types/proper-lockfile": "4.1.4",
"@types/tldjs": "2.3.4",
"@typescript-eslint/eslint-plugin": "5.62.0",
"@typescript-eslint/parser": "5.62.0",
"@typescript-eslint/eslint-plugin": "8.23.0",
"@typescript-eslint/parser": "8.23.0",
"clean-webpack-plugin": "4.0.0",
"concurrently": "9.1.0",
"concurrently": "9.1.2",
"copy-webpack-plugin": "12.0.2",
"cross-env": "7.0.3",
"css-loader": "7.1.2",
"dotenv": "16.4.5",
"electron": "28.3.3",
"dotenv": "16.4.7",
"electron": "34.1.1",
"electron-builder": "24.13.3",
"electron-log": "5.2.4",
"electron-reload": "2.0.0-alpha.1",
"electron-store": "8.2.0",
"electron-updater": "6.3.9",
"eslint": "8.57.1",
"eslint-config-prettier": "9.1.0",
"eslint-import-resolver-typescript": "3.6.3",
"eslint-config-prettier": "10.0.1",
"eslint-import-resolver-typescript": "3.7.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-rxjs": "5.0.3",
"eslint-plugin-rxjs-angular": "2.0.1",
@@ -117,63 +117,64 @@
"jest": "29.7.0",
"jest-junit": "16.0.0",
"jest-mock-extended": "3.0.7",
"jest-preset-angular": "14.4.2",
"lint-staged": "15.2.10",
"jest-preset-angular": "14.5.0",
"lint-staged": "15.4.1",
"mini-css-extract-plugin": "2.9.2",
"minimatch": "3.1.2",
"node-abi": "3.74.0",
"node-forge": "1.3.1",
"node-loader": "2.1.0",
"pkg": "5.8.1",
"prettier": "3.3.3",
"rimraf": "5.0.10",
"prettier": "3.4.2",
"rimraf": "6.0.1",
"rxjs": "7.8.1",
"sass": "1.79.4",
"sass-loader": "16.0.4",
"ts-jest": "29.2.5",
"ts-loader": "9.5.1",
"ts-loader": "9.5.2",
"tsconfig-paths-webpack-plugin": "4.2.0",
"type-fest": "4.30.0",
"typescript": "5.5.4",
"typescript-transform-paths": "3.5.2",
"webpack": "5.95.0",
"webpack-cli": "5.1.4",
"type-fest": "4.32.0",
"typescript": "5.4.5",
"webpack": "5.97.1",
"webpack-cli": "6.0.1",
"webpack-merge": "6.0.1",
"zone.js": "0.15.0",
"webpack-node-externals": "3.0.0"
"webpack-node-externals": "3.0.0",
"zone.js": "0.14.10"
},
"dependencies": {
"@angular/animations": "19.0.3",
"@angular/cdk": "19.0.2",
"@angular/common": "19.0.3",
"@angular/compiler": "19.0.3",
"@angular/core": "19.0.3",
"@angular/forms": "19.0.3",
"@angular/platform-browser": "19.0.3",
"@angular/platform-browser-dynamic": "19.0.3",
"@angular/router": "19.0.3",
"@angular/animations": "17.3.12",
"@angular/cdk": "17.3.10",
"@angular/common": "17.3.12",
"@angular/compiler": "17.3.12",
"@angular/core": "17.3.12",
"@angular/forms": "17.3.12",
"@angular/platform-browser": "17.3.12",
"@angular/platform-browser-dynamic": "17.3.12",
"@angular/router": "17.3.12",
"@microsoft/microsoft-graph-client": "3.0.7",
"big-integer": "1.6.52",
"bootstrap": "5.3.3",
"browser-hrtime": "1.1.8",
"chalk": "4.1.2",
"commander": "12.1.0",
"core-js": "3.38.1",
"commander": "13.1.0",
"core-js": "3.40.0",
"form-data": "4.0.1",
"google-auth-library": "7.14.1",
"googleapis": "73.0.0",
"https-proxy-agent": "7.0.5",
"google-auth-library": "9.15.1",
"googleapis": "144.0.0",
"https-proxy-agent": "7.0.6",
"inquirer": "8.2.6",
"keytar": "7.9.0",
"ldapts": "7.2.1",
"ldapts": "7.3.1",
"lowdb": "1.0.0",
"ngx-toastr": "17.0.2",
"ngx-toastr": "19.0.0",
"node-fetch": "2.7.0",
"proper-lockfile": "4.1.2",
"rxjs": "7.8.1",
"tldjs": "2.3.1",
"zone.js": "0.15.0"
"zone.js": "0.14.10"
},
"engines": {
"node": "~20.18.0",
"node": "~22.13.0",
"npm": "~10"
},
"lint-staged": {

View File

@@ -1,7 +1,7 @@
{
"name": "@bitwarden/directory-connector",
"version": "2.9.5",
"lockfileVersion": 2,
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {

View File

@@ -0,0 +1,6 @@
import { DirectoryType } from "@/src/enums/directoryType";
import { IDirectoryService } from "@/src/services/directory.service";
export abstract class DirectoryFactoryService {
abstract createService(type: DirectoryType): IDirectoryService;
}

View File

@@ -0,0 +1,13 @@
import { OrganizationImportRequest } from "@/jslib/common/src/models/request/organizationImportRequest";
import { GroupEntry } from "@/src/models/groupEntry";
import { UserEntry } from "@/src/models/userEntry";
export abstract class RequestBuilder {
buildRequest: (
groups: GroupEntry[],
users: UserEntry[],
removeDisabled: boolean,
overwriteExisting: boolean,
) => OrganizationImportRequest[];
}

View File

@@ -25,6 +25,11 @@ import { ElectronRendererStorageService } from "@/jslib/electron/src/services/el
import { NodeApiService } from "@/jslib/node/src/services/nodeApi.service";
import { NodeCryptoFunctionService } from "@/jslib/node/src/services/nodeCryptoFunction.service";
import { DirectoryFactoryService } from "@/src/abstractions/directory-factory.service";
import { BatchRequestBuilder } from "@/src/services/batch-request-builder";
import { DefaultDirectoryFactoryService } from "@/src/services/directory-factory.service";
import { SingleRequestBuilder } from "@/src/services/single-request-builder";
import { AuthService as AuthServiceAbstraction } from "../../abstractions/auth.service";
import { StateService as StateServiceAbstraction } from "../../abstractions/state.service";
import { Account } from "../../models/account";
@@ -168,13 +173,15 @@ export function initFactory(
provide: SyncService,
useClass: SyncService,
deps: [
LogServiceAbstraction,
CryptoFunctionServiceAbstraction,
ApiServiceAbstraction,
MessagingServiceAbstraction,
I18nServiceAbstraction,
EnvironmentServiceAbstraction,
StateServiceAbstraction,
BatchRequestBuilder,
SingleRequestBuilder,
DirectoryFactoryService,
],
}),
safeProvider(AuthGuardService),
@@ -215,6 +222,19 @@ export function initFactory(
StateMigrationServiceAbstraction,
],
}),
safeProvider({
provide: SingleRequestBuilder,
deps: [],
}),
safeProvider({
provide: BatchRequestBuilder,
deps: [],
}),
safeProvider({
provide: DirectoryFactoryService,
useClass: DefaultDirectoryFactoryService,
deps: [LogServiceAbstraction, I18nServiceAbstraction, StateServiceAbstraction],
}),
] satisfies SafeProvider[],
})
export class ServicesModule {}

View File

@@ -17,12 +17,16 @@ import { ConsoleLogService } from "@/jslib/node/src/cli/services/consoleLog.serv
import { NodeApiService } from "@/jslib/node/src/services/nodeApi.service";
import { NodeCryptoFunctionService } from "@/jslib/node/src/services/nodeCryptoFunction.service";
import { DirectoryFactoryService } from "./abstractions/directory-factory.service";
import { Account } from "./models/account";
import { Program } from "./program";
import { AuthService } from "./services/auth.service";
import { BatchRequestBuilder } from "./services/batch-request-builder";
import { DefaultDirectoryFactoryService } from "./services/directory-factory.service";
import { I18nService } from "./services/i18n.service";
import { KeytarSecureStorageService } from "./services/keytarSecureStorage.service";
import { LowdbStorageService } from "./services/lowdbStorage.service";
import { SingleRequestBuilder } from "./services/single-request-builder";
import { StateService } from "./services/state.service";
import { StateMigrationService } from "./services/stateMigration.service";
import { SyncService } from "./services/sync.service";
@@ -51,6 +55,9 @@ export class Main {
syncService: SyncService;
stateService: StateService;
stateMigrationService: StateMigrationService;
directoryFactoryService: DirectoryFactoryService;
batchRequestBuilder: BatchRequestBuilder;
singleRequestBuilder: SingleRequestBuilder;
constructor() {
const applicationName = "Bitwarden Directory Connector";
@@ -146,14 +153,25 @@ export class Main {
this.stateService,
);
this.syncService = new SyncService(
this.directoryFactoryService = new DefaultDirectoryFactoryService(
this.logService,
this.i18nService,
this.stateService,
);
this.batchRequestBuilder = new BatchRequestBuilder();
this.singleRequestBuilder = new SingleRequestBuilder();
this.syncService = new SyncService(
this.cryptoFunctionService,
this.apiService,
this.messagingService,
this.i18nService,
this.environmentService,
this.stateService,
this.batchRequestBuilder,
this.singleRequestBuilder,
this.directoryFactoryService,
);
this.program = new Program(this);

View File

@@ -0,0 +1,70 @@
import { OrganizationImportRequest } from "@/jslib/common/src/models/request/organizationImportRequest";
import { GroupEntry } from "@/src/models/groupEntry";
import { UserEntry } from "@/src/models/userEntry";
import { RequestBuilder } from "../abstractions/request-builder.service";
import { batchSize } from "./sync.service";
/**
* This class is responsible for batching large sync requests (>2k users) into multiple smaller
* requests to the /import endpoint. This is done to ensure we are under the default
* maximum packet size for NGINX web servers to avoid the request potentially timing out
* */
export class BatchRequestBuilder implements RequestBuilder {
buildRequest(
groups: GroupEntry[],
users: UserEntry[],
removeDisabled: boolean,
overwriteExisting: boolean,
): OrganizationImportRequest[] {
const requests: OrganizationImportRequest[] = [];
if (users.length > 0) {
const usersRequest = users.map((u) => {
return {
email: u.email,
externalId: u.externalId,
deleted: u.deleted || (removeDisabled && u.disabled),
};
});
// Partition users
for (let i = 0; i < usersRequest.length; i += batchSize) {
const u = usersRequest.slice(i, i + batchSize);
const req = new OrganizationImportRequest({
groups: [],
users: u,
largeImport: true,
overwriteExisting,
});
requests.push(req);
}
}
if (groups.length > 0) {
const groupRequest = groups.map((g) => {
return {
name: g.name,
externalId: g.externalId,
memberExternalIds: Array.from(g.userMemberExternalIds),
};
});
// Partition groups
for (let i = 0; i < groupRequest.length; i += batchSize) {
const g = groupRequest.slice(i, i + batchSize);
const req = new OrganizationImportRequest({
groups: g,
users: [],
largeImport: true,
overwriteExisting,
});
requests.push(req);
}
}
return requests;
}
}

View File

@@ -0,0 +1,47 @@
import { GroupEntry } from "@/src/models/groupEntry";
import { UserEntry } from "@/src/models/userEntry";
import { BatchRequestBuilder } from "./batch-request-builder";
import { SingleRequestBuilder } from "./single-request-builder";
describe("BatchRequestBuilder", () => {
let batchRequestBuilder: BatchRequestBuilder;
let singleRequestBuilder: SingleRequestBuilder;
function userSimulator(userCount: number) {
return Array(userCount).fill(new UserEntry());
}
function groupSimulator(groupCount: number) {
return Array(groupCount).fill(new GroupEntry());
}
beforeEach(async () => {
batchRequestBuilder = new BatchRequestBuilder();
singleRequestBuilder = new SingleRequestBuilder();
});
it("BatchRequestBuilder batches requests for > 2000 users", () => {
const mockGroups = groupSimulator(11000);
const mockUsers = userSimulator(11000);
const requests = batchRequestBuilder.buildRequest(mockGroups, mockUsers, true, true);
expect(requests.length).toEqual(12);
});
it("SingleRequestBuilder returns single request for 200 users", () => {
const mockGroups = groupSimulator(200);
const mockUsers = userSimulator(200);
const requests = singleRequestBuilder.buildRequest(mockGroups, mockUsers, true, true);
expect(requests.length).toEqual(1);
});
it("BatchRequestBuilder retuns an empty array when there are no users or groups", () => {
const requests = batchRequestBuilder.buildRequest([], [], true, true);
expect(requests).toEqual([]);
});
});

View File

@@ -0,0 +1,37 @@
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
import { LogService } from "@/jslib/common/src/abstractions/log.service";
import { DirectoryFactoryService } from "../abstractions/directory-factory.service";
import { StateService } from "../abstractions/state.service";
import { DirectoryType } from "../enums/directoryType";
import { AzureDirectoryService } from "./azure-directory.service";
import { GSuiteDirectoryService } from "./gsuite-directory.service";
import { LdapDirectoryService } from "./ldap-directory.service";
import { OktaDirectoryService } from "./okta-directory.service";
import { OneLoginDirectoryService } from "./onelogin-directory.service";
export class DefaultDirectoryFactoryService implements DirectoryFactoryService {
constructor(
private logService: LogService,
private i18nService: I18nService,
private stateService: StateService,
) {}
createService(directoryType: DirectoryType) {
switch (directoryType) {
case DirectoryType.GSuite:
return new GSuiteDirectoryService(this.logService, this.i18nService, this.stateService);
case DirectoryType.AzureActiveDirectory:
return new AzureDirectoryService(this.logService, this.i18nService, this.stateService);
case DirectoryType.Ldap:
return new LdapDirectoryService(this.logService, this.i18nService, this.stateService);
case DirectoryType.Okta:
return new OktaDirectoryService(this.logService, this.i18nService, this.stateService);
case DirectoryType.OneLogin:
return new OneLoginDirectoryService(this.logService, this.i18nService, this.stateService);
default:
throw new Error("Invalid Directory Type");
}
}
}

View File

@@ -141,7 +141,7 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
entry.referenceId = user.id;
entry.externalId = user.id;
entry.email = user.primaryEmail != null ? user.primaryEmail.trim().toLowerCase() : null;
entry.disabled = user.suspended || false;
entry.disabled = user.suspended || user.archived || false;
entry.deleted = deleted;
return entry;
}

View File

@@ -5,8 +5,7 @@ import { LogService } from "../../jslib/common/src/abstractions/log.service";
import { groupFixtures } from "../../openldap/group-fixtures";
import { userFixtures } from "../../openldap/user-fixtures";
import { DirectoryType } from "../enums/directoryType";
import { LdapConfiguration } from "../models/ldapConfiguration";
import { SyncConfiguration } from "../models/syncConfiguration";
import { getLdapConfiguration, getSyncConfiguration } from "../utils/test-fixtures";
import { LdapDirectoryService } from "./ldap-directory.service";
import { StateService } from "./state.service";
@@ -154,54 +153,3 @@ describe("ldapDirectoryService", () => {
});
});
});
/**
* @returns a basic ldap configuration without TLS/SSL enabled. Can be overridden by passing in a partial configuration.
*/
const getLdapConfiguration = (config?: Partial<LdapConfiguration>): LdapConfiguration => ({
ssl: false,
startTls: false,
tlsCaPath: null,
sslAllowUnauthorized: false,
sslCertPath: null,
sslKeyPath: null,
sslCaPath: null,
hostname: "localhost",
port: 1389,
domain: null,
rootPath: "dc=bitwarden,dc=com",
currentUser: false,
username: "cn=admin,dc=bitwarden,dc=com",
password: "admin",
ad: false,
pagedSearch: false,
...(config ?? {}),
});
/**
* @returns a basic sync configuration. Can be overridden by passing in a partial configuration.
*/
const getSyncConfiguration = (config?: Partial<SyncConfiguration>): SyncConfiguration => ({
users: false,
groups: false,
interval: 5,
userFilter: null,
groupFilter: null,
removeDisabled: false,
overwriteExisting: false,
largeImport: false,
// Ldap properties
groupObjectClass: "posixGroup",
userObjectClass: "person",
groupPath: null,
userPath: null,
groupNameAttribute: "cn",
userEmailAttribute: "mail",
memberAttribute: "memberUid",
useEmailPrefixSuffix: false,
emailPrefixAttribute: "sAMAccountName",
emailSuffix: null,
creationDateAttribute: "whenCreated",
revisionDateAttribute: "whenChanged",
...(config ?? {}),
});

View File

@@ -18,6 +18,11 @@ import { IDirectoryService } from "./directory.service";
const UserControlAccountDisabled = 2;
/**
* The attribute name for the unique identifier used by Active Directory.
*/
const ActiveDirectoryExternalId = "objectGUID";
export class LdapDirectoryService implements IDirectoryService {
private client: ldapts.Client;
private dirConfig: LdapConfiguration;
@@ -240,7 +245,7 @@ export class LdapDirectoryService implements IDirectoryService {
* otherwise it falls back to the provided referenceId.
*/
private getExternalId(searchEntry: ldapts.Entry, referenceId: string) {
const attr = this.getAttr<Buffer>(searchEntry, "objectGUID");
const attr = this.getAttr<Buffer>(searchEntry, ActiveDirectoryExternalId);
if (attr != null) {
return this.bufToGuid(attr);
} else {
@@ -358,6 +363,9 @@ export class LdapDirectoryService implements IDirectoryService {
filter: filter,
scope: "sub",
paged: this.dirConfig.pagedSearch,
// We need to expressly tell ldapts what attributes to return as Buffer objects,
// otherwise they are returned as strings
explicitBufferAttributes: [ActiveDirectoryExternalId],
};
const { searchEntries } = await this.client.search(path, options, controls);
return searchEntries.map((e) => processEntry(e)).filter((e) => e != null);

View File

@@ -0,0 +1,42 @@
import { OrganizationImportRequest } from "@/jslib/common/src/models/request/organizationImportRequest";
import { GroupEntry } from "@/src/models/groupEntry";
import { UserEntry } from "@/src/models/userEntry";
import { RequestBuilder } from "../abstractions/request-builder.service";
/**
* This class is responsible for building small (<2k users) syncs as a single
* request to the /import endpoint. This is done to be backwards compatible with
* existing functionality for sync requests that are sufficiently small enough to not
* exceed default maximum packet size limits on NGINX web servers.
* */
export class SingleRequestBuilder implements RequestBuilder {
buildRequest(
groups: GroupEntry[],
users: UserEntry[],
removeDisabled: boolean,
overwriteExisting: boolean,
): OrganizationImportRequest[] {
return [
new OrganizationImportRequest({
groups: (groups ?? []).map((g) => {
return {
name: g.name,
externalId: g.externalId,
memberExternalIds: Array.from(g.userMemberExternalIds),
};
}),
users: (users ?? []).map((u) => {
return {
email: u.email,
externalId: u.externalId,
deleted: u.deleted || (removeDisabled && u.disabled),
};
}),
overwriteExisting: overwriteExisting,
largeImport: false,
}),
];
}
}

View File

@@ -0,0 +1,132 @@
import { mock, MockProxy } from "jest-mock-extended";
import { CryptoFunctionService } from "@/jslib/common/src/abstractions/cryptoFunction.service";
import { EnvironmentService } from "@/jslib/common/src/abstractions/environment.service";
import { MessagingService } from "@/jslib/common/src/abstractions/messaging.service";
import { OrganizationImportRequest } from "@/jslib/common/src/models/request/organizationImportRequest";
import { ApiService } from "@/jslib/common/src/services/api.service";
import { DirectoryFactoryService } from "../abstractions/directory-factory.service";
import { DirectoryType } from "../enums/directoryType";
import { getSyncConfiguration } from "../utils/test-fixtures";
import { BatchRequestBuilder } from "./batch-request-builder";
import { I18nService } from "./i18n.service";
import { LdapDirectoryService } from "./ldap-directory.service";
import { SingleRequestBuilder } from "./single-request-builder";
import { StateService } from "./state.service";
import { SyncService } from "./sync.service";
import * as constants from "./sync.service";
import { groupFixtures } from "@/openldap/group-fixtures";
import { userFixtures } from "@/openldap/user-fixtures";
describe("SyncService", () => {
let cryptoFunctionService: MockProxy<CryptoFunctionService>;
let apiService: MockProxy<ApiService>;
let messagingService: MockProxy<MessagingService>;
let i18nService: MockProxy<I18nService>;
let environmentService: MockProxy<EnvironmentService>;
let stateService: MockProxy<StateService>;
let directoryFactory: MockProxy<DirectoryFactoryService>;
let batchRequestBuilder: MockProxy<BatchRequestBuilder>;
let singleRequestBuilder: MockProxy<SingleRequestBuilder>;
let syncService: SyncService;
beforeEach(() => {
cryptoFunctionService = mock();
apiService = mock();
messagingService = mock();
i18nService = mock();
environmentService = mock();
stateService = mock();
directoryFactory = mock();
batchRequestBuilder = mock();
singleRequestBuilder = mock();
stateService.getDirectoryType.mockResolvedValue(DirectoryType.Ldap);
stateService.getOrganizationId.mockResolvedValue("fakeId");
const mockDirectoryService = mock<LdapDirectoryService>();
mockDirectoryService.getEntries.mockResolvedValue([groupFixtures, userFixtures]);
directoryFactory.createService.mockReturnValue(mockDirectoryService);
syncService = new SyncService(
cryptoFunctionService,
apiService,
messagingService,
i18nService,
environmentService,
stateService,
batchRequestBuilder,
singleRequestBuilder,
directoryFactory,
);
});
it("Sync posts single request successfully for unique hashes", async () => {
stateService.getSync.mockResolvedValue(getSyncConfiguration({ groups: true, users: true }));
cryptoFunctionService.hash.mockResolvedValue(new ArrayBuffer(1));
// This arranges the last hash to be differet from the ArrayBuffer after it is converted to b64
stateService.getLastSyncHash.mockResolvedValue("unique hash");
const mockRequest: OrganizationImportRequest[] = [
{
members: [],
groups: [],
overwriteExisting: true,
largeImport: true,
},
];
singleRequestBuilder.buildRequest.mockReturnValue(mockRequest);
await syncService.sync(true, false);
expect(apiService.postPublicImportDirectory).toHaveBeenCalledTimes(1);
});
it("Sync posts multiple request successfully for unique hashes", async () => {
stateService.getSync.mockResolvedValue(
getSyncConfiguration({ groups: true, users: true, largeImport: true }),
);
cryptoFunctionService.hash.mockResolvedValue(new ArrayBuffer(1));
// This arranges the last hash to be differet from the ArrayBuffer after it is converted to b64
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.
constants.batchSize = 4;
const mockRequests = new Array(6).fill({
members: [],
groups: [],
overwriteExisting: true,
largeImport: true,
});
batchRequestBuilder.buildRequest.mockReturnValue(mockRequests);
await syncService.sync(true, false);
expect(apiService.postPublicImportDirectory).toHaveBeenCalledTimes(6);
expect(apiService.postPublicImportDirectory).toHaveBeenCalledWith(mockRequests[0]);
expect(apiService.postPublicImportDirectory).toHaveBeenCalledWith(mockRequests[1]);
expect(apiService.postPublicImportDirectory).toHaveBeenCalledWith(mockRequests[2]);
expect(apiService.postPublicImportDirectory).toHaveBeenCalledWith(mockRequests[3]);
expect(apiService.postPublicImportDirectory).toHaveBeenCalledWith(mockRequests[4]);
expect(apiService.postPublicImportDirectory).toHaveBeenCalledWith(mockRequests[5]);
});
it("does not post for the same hash", async () => {
// @ts-expect-error this sets the batch size back to its expexted value for this test.
constants.batchSize = 2000;
stateService.getSync.mockResolvedValue(getSyncConfiguration({ groups: true, users: true }));
cryptoFunctionService.hash.mockResolvedValue(new ArrayBuffer(1));
// This arranges the last hash to be the same as the ArrayBuffer after it is converted to b64
stateService.getLastSyncHash.mockResolvedValue("AA==");
await syncService.sync(true, false);
expect(apiService.postPublicImportDirectory).not.toHaveBeenCalled();
});
});

View File

@@ -2,35 +2,40 @@ import { ApiService } from "@/jslib/common/src/abstractions/api.service";
import { CryptoFunctionService } from "@/jslib/common/src/abstractions/cryptoFunction.service";
import { EnvironmentService } from "@/jslib/common/src/abstractions/environment.service";
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
import { LogService } from "@/jslib/common/src/abstractions/log.service";
import { MessagingService } from "@/jslib/common/src/abstractions/messaging.service";
import { Utils } from "@/jslib/common/src/misc/utils";
import { OrganizationImportRequest } from "@/jslib/common/src/models/request/organizationImportRequest";
import { DirectoryFactoryService } from "../abstractions/directory-factory.service";
import { StateService } from "../abstractions/state.service";
import { DirectoryType } from "../enums/directoryType";
import { GroupEntry } from "../models/groupEntry";
import { SyncConfiguration } from "../models/syncConfiguration";
import { UserEntry } from "../models/userEntry";
import { AzureDirectoryService } from "./azure-directory.service";
import { IDirectoryService } from "./directory.service";
import { GSuiteDirectoryService } from "./gsuite-directory.service";
import { LdapDirectoryService } from "./ldap-directory.service";
import { OktaDirectoryService } from "./okta-directory.service";
import { OneLoginDirectoryService } from "./onelogin-directory.service";
import { BatchRequestBuilder } from "./batch-request-builder";
import { SingleRequestBuilder } from "./single-request-builder";
export interface HashResult {
hash: string;
hashLegacy: string;
}
export const batchSize = 2000;
export class SyncService {
private dirType: DirectoryType;
constructor(
private logService: LogService,
private cryptoFunctionService: CryptoFunctionService,
private apiService: ApiService,
private messagingService: MessagingService,
private i18nService: I18nService,
private environmentService: EnvironmentService,
private stateService: StateService,
private batchRequestBuilder: BatchRequestBuilder,
private singleRequestBuilder: SingleRequestBuilder,
private directoryFactory: DirectoryFactoryService,
) {}
async sync(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
@@ -39,7 +44,7 @@ export class SyncService {
throw new Error("No directory configured.");
}
const directoryService = this.getDirectoryService();
const directoryService = this.directoryFactory.createService(this.dirType);
if (directoryService == null) {
throw new Error("Cannot load directory service.");
}
@@ -78,42 +83,21 @@ export class SyncService {
return [groups, users];
}
const req = this.buildRequest(
const reqs = this.buildRequest(
groups,
users,
syncConfig.removeDisabled,
syncConfig.overwriteExisting,
syncConfig.largeImport,
);
const reqJson = JSON.stringify(req);
const orgId = await this.stateService.getOrganizationId();
if (orgId == null) {
throw new Error("Organization not set.");
}
const result: HashResult = await this.generateHash(reqs);
// TODO: Remove hashLegacy once we're sure clients have had time to sync new hashes
let hashLegacy: string = null;
const hashBuffLegacy = await this.cryptoFunctionService.hash(
this.environmentService.getApiUrl() + reqJson,
"sha256",
);
if (hashBuffLegacy != null) {
hashLegacy = Utils.fromBufferToB64(hashBuffLegacy);
}
let hash: string = null;
const hashBuff = await this.cryptoFunctionService.hash(
this.environmentService.getApiUrl() + orgId + reqJson,
"sha256",
);
if (hashBuff != null) {
hash = Utils.fromBufferToB64(hashBuff);
}
const lastHash = await this.stateService.getLastSyncHash();
if (lastHash == null || (hash !== lastHash && hashLegacy !== lastHash)) {
await this.apiService.postPublicImportDirectory(req);
await this.stateService.setLastSyncHash(hash);
if (result.hash && (await this.isNewHash(result))) {
for (const req of reqs) {
await this.apiService.postPublicImportDirectory(req);
}
await this.stateService.setLastSyncHash(result.hash);
} else {
groups = null;
users = null;
@@ -133,6 +117,40 @@ export class SyncService {
}
}
async generateHash(reqs: OrganizationImportRequest[]): Promise<HashResult> {
const reqJson = JSON.stringify(reqs?.length === 1 ? reqs[0] : reqs);
const orgId = await this.stateService.getOrganizationId();
if (orgId == null) {
throw new Error("Organization not set.");
}
// TODO: Remove hashLegacy once we're sure clients have had time to sync new hashes
let hashLegacy: string = null;
const hashBuffLegacy = await this.cryptoFunctionService.hash(
this.environmentService.getApiUrl() + reqJson,
"sha256",
);
if (hashBuffLegacy != null) {
hashLegacy = Utils.fromBufferToB64(hashBuffLegacy);
}
let hash: string = null;
const hashBuff = await this.cryptoFunctionService.hash(
this.environmentService.getApiUrl() + orgId + reqJson,
"sha256",
);
if (hashBuff != null) {
hash = Utils.fromBufferToB64(hashBuff);
}
return { hash, hashLegacy };
}
async isNewHash(hashResult: HashResult): Promise<boolean> {
const lastHash = await this.stateService.getLastSyncHash();
return lastHash == null || (hashResult.hash !== lastHash && hashResult.hashLegacy !== lastHash);
}
private removeDuplicateUsers(users: UserEntry[]) {
if (users == null) {
return null;
@@ -198,48 +216,28 @@ export class SyncService {
return allUsers;
}
private getDirectoryService(): IDirectoryService {
switch (this.dirType) {
case DirectoryType.GSuite:
return new GSuiteDirectoryService(this.logService, this.i18nService, this.stateService);
case DirectoryType.AzureActiveDirectory:
return new AzureDirectoryService(this.logService, this.i18nService, this.stateService);
case DirectoryType.Ldap:
return new LdapDirectoryService(this.logService, this.i18nService, this.stateService);
case DirectoryType.Okta:
return new OktaDirectoryService(this.logService, this.i18nService, this.stateService);
case DirectoryType.OneLogin:
return new OneLoginDirectoryService(this.logService, this.i18nService, this.stateService);
default:
return null;
}
}
private buildRequest(
groups: GroupEntry[],
users: UserEntry[],
removeDisabled: boolean,
overwriteExisting: boolean,
largeImport = false,
) {
return new OrganizationImportRequest({
groups: (groups ?? []).map((g) => {
return {
name: g.name,
externalId: g.externalId,
memberExternalIds: Array.from(g.userMemberExternalIds),
};
}),
users: (users ?? []).map((u) => {
return {
email: u.email,
externalId: u.externalId,
deleted: u.deleted || (removeDisabled && u.disabled),
};
}),
overwriteExisting: overwriteExisting,
largeImport: largeImport,
});
): OrganizationImportRequest[] {
if (largeImport && groups.length + users.length > batchSize) {
return this.batchRequestBuilder.buildRequest(
groups,
users,
overwriteExisting,
removeDisabled,
);
} else {
return this.singleRequestBuilder.buildRequest(
groups,
users,
overwriteExisting,
removeDisabled,
);
}
}
private async saveSyncTimes(syncConfig: SyncConfiguration, time: Date) {

View File

@@ -0,0 +1,53 @@
import { LdapConfiguration } from "../models/ldapConfiguration";
import { SyncConfiguration } from "../models/syncConfiguration";
/**
* @returns a basic ldap configuration without TLS/SSL enabled. Can be overridden by passing in a partial configuration.
*/
export const getLdapConfiguration = (config?: Partial<LdapConfiguration>): LdapConfiguration => ({
ssl: false,
startTls: false,
tlsCaPath: null,
sslAllowUnauthorized: false,
sslCertPath: null,
sslKeyPath: null,
sslCaPath: null,
hostname: "localhost",
port: 1389,
domain: null,
rootPath: "dc=bitwarden,dc=com",
currentUser: false,
username: "cn=admin,dc=bitwarden,dc=com",
password: "admin",
ad: false,
pagedSearch: false,
...(config ?? {}),
});
/**
* @returns a basic sync configuration. Can be overridden by passing in a partial configuration.
*/
export const getSyncConfiguration = (config?: Partial<SyncConfiguration>): SyncConfiguration => ({
users: false,
groups: false,
interval: 5,
userFilter: null,
groupFilter: null,
removeDisabled: false,
overwriteExisting: false,
largeImport: false,
// Ldap properties
groupObjectClass: "posixGroup",
userObjectClass: "person",
groupPath: null,
userPath: null,
groupNameAttribute: "cn",
userEmailAttribute: "mail",
memberAttribute: "memberUid",
useEmailPrefixSuffix: false,
emailPrefixAttribute: "sAMAccountName",
emailSuffix: null,
creationDateAttribute: "whenCreated",
revisionDateAttribute: "whenChanged",
...(config ?? {}),
});

View File

@@ -17,12 +17,7 @@
"paths": {
"tldjs": ["@/jslib/src/misc/tldjs.noop"],
"@/*": ["./*"]
},
"plugins": [
{
"transform": "typescript-transform-paths"
}
]
}
},
"include": ["src", "jslib", "scripts", "./*.ts"]
}