mirror of
https://github.com/bitwarden/directory-connector
synced 2025-12-15 15:53:41 +00:00
Compare commits
180 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8072c523cc | ||
|
|
f96429a47b | ||
|
|
5e64dc9262 | ||
|
|
9c7cd943b3 | ||
|
|
7cf3166169 | ||
|
|
9bdb77a573 | ||
|
|
3b8ee5ec0d | ||
|
|
6e7e09064f | ||
|
|
dfcb450a8a | ||
|
|
b192c34c15 | ||
|
|
f813dbb690 | ||
|
|
16deafca76 | ||
|
|
647b087fa7 | ||
|
|
4bd1387b83 | ||
|
|
4e098462dc | ||
|
|
0b1c2ae72a | ||
|
|
5d3fa0a0d2 | ||
|
|
6097bca063 | ||
|
|
a6aafe7593 | ||
|
|
56d05af07a | ||
|
|
0d17345600 | ||
|
|
5df62b7422 | ||
|
|
868914feb1 | ||
|
|
1a9555d4af | ||
|
|
33c8f15e45 | ||
|
|
ed8dd01dbd | ||
|
|
2296e37e8f | ||
|
|
a0f33c7bdc | ||
|
|
f6b249836e | ||
|
|
0c92a97054 | ||
|
|
24ab152559 | ||
|
|
5f9f09d77c | ||
|
|
4d27d9e48d | ||
|
|
0b624b972a | ||
|
|
1889e12bac | ||
|
|
7648f73072 | ||
|
|
75d346ed85 | ||
|
|
dabfe7907d | ||
|
|
965976223f | ||
|
|
410f00c213 | ||
|
|
0d8b942ad4 | ||
|
|
5371015a58 | ||
|
|
2ead70e434 | ||
|
|
ffca14cb5f | ||
|
|
090d5e82df | ||
|
|
8893ddf0f7 | ||
|
|
997ec5a699 | ||
|
|
762818ee39 | ||
|
|
61c6ba8189 | ||
|
|
9cfa646bcb | ||
|
|
b4301c7d41 | ||
|
|
71b5f6a38a | ||
|
|
1c0052fe30 | ||
|
|
35862acb73 | ||
|
|
11cf64fcc7 | ||
|
|
2ab37b45cf | ||
|
|
7096fc830b | ||
|
|
39806b7d96 | ||
|
|
7a16b8cb0e | ||
|
|
2583068dbd | ||
|
|
e5d0b3a372 | ||
|
|
9a1caf1e7e | ||
|
|
af0e41e26c | ||
|
|
d3049164a9 | ||
|
|
bdfda6775d | ||
|
|
72e6e74a42 | ||
|
|
75832fbed9 | ||
|
|
811b3adb51 | ||
|
|
e710df3ba7 | ||
|
|
4ee0c3ccba | ||
|
|
036b934119 | ||
|
|
4bfd43bf4c | ||
|
|
55722d3c04 | ||
|
|
77043d8d66 | ||
|
|
002117a6e5 | ||
|
|
5aa8097cfd | ||
|
|
19e1049566 | ||
|
|
87bdc88e22 | ||
|
|
950e3ae91e | ||
|
|
a37532e1ad | ||
|
|
21ff5f311b | ||
|
|
7dd12cf0cb | ||
|
|
6f8df7a690 | ||
|
|
1ff0ef1f90 | ||
|
|
196fc10d80 | ||
|
|
7496de4cc6 | ||
|
|
02a6adf6a2 | ||
|
|
6c95575a8f | ||
|
|
39755e89a8 | ||
|
|
c5d3ca218e | ||
|
|
87a2a2a0e4 | ||
|
|
5904da2eb1 | ||
|
|
1ac0c81661 | ||
|
|
955711714d | ||
|
|
5848553a4b | ||
|
|
38758caac4 | ||
|
|
b41a1bdbf4 | ||
|
|
a400ab7f7d | ||
|
|
e5d0405882 | ||
|
|
f2137c02f7 | ||
|
|
4c61f465a3 | ||
|
|
6d22041eab | ||
|
|
752e26db6d | ||
|
|
094ec23f04 | ||
|
|
af8ff2901e | ||
|
|
628689c990 | ||
|
|
ab37221182 | ||
|
|
626892473f | ||
|
|
c621677852 | ||
|
|
6650b4848d | ||
|
|
c07c56f89b | ||
|
|
294590882f | ||
|
|
9151b9c2d6 | ||
|
|
5817468d09 | ||
|
|
31dd20999c | ||
|
|
4eb9c9bd4d | ||
|
|
150164534f | ||
|
|
2b2d8a9fab | ||
|
|
fb122cbbdb | ||
|
|
0b37857d29 | ||
|
|
15c1876687 | ||
|
|
473a6e391d | ||
|
|
13ad64e6f3 | ||
|
|
04e278249e | ||
|
|
e12c4ea1e2 | ||
|
|
acfa8632af | ||
|
|
f53c1f5605 | ||
|
|
f0f7f89ea8 | ||
|
|
059ff0647a | ||
|
|
d94e5b0620 | ||
|
|
3840bce6d7 | ||
|
|
71cd11eedf | ||
|
|
ecea04bc08 | ||
|
|
0575ee5507 | ||
|
|
8b2fa8405b | ||
|
|
7d36a50687 | ||
|
|
f7dd9d8d5b | ||
|
|
379b4f4612 | ||
|
|
4652668e1b | ||
|
|
4e02a8571e | ||
|
|
bc927a65ac | ||
|
|
90f1a1c115 | ||
|
|
c48acf6038 | ||
|
|
2640e8c890 | ||
|
|
094ec55e7f | ||
|
|
fe39bdac42 | ||
|
|
2c98d50c43 | ||
|
|
d374cff51c | ||
|
|
d9e7256804 | ||
|
|
0ef2d1523e | ||
|
|
05bad6f671 | ||
|
|
5a62cfcda1 | ||
|
|
634d38510d | ||
|
|
bf27872973 | ||
|
|
20bb5a4926 | ||
|
|
f63fb3ffa0 | ||
|
|
f11c32c606 | ||
|
|
0b4e22a952 | ||
|
|
a85dbff3a5 | ||
|
|
d2835dd577 | ||
|
|
62abc99b61 | ||
|
|
20de62cc79 | ||
|
|
731614279f | ||
|
|
2be751dc8e | ||
|
|
e0409941a3 | ||
|
|
e6a5a3c8c1 | ||
|
|
3f3590a223 | ||
|
|
c1f64d7b82 | ||
|
|
f90611c96f | ||
|
|
630e21f7c1 | ||
|
|
2e81642c0e | ||
|
|
39514b9550 | ||
|
|
2da82d5610 | ||
|
|
69f33a08b6 | ||
|
|
a05e9c7746 | ||
|
|
d8031e4f49 | ||
|
|
e6aa07ba5c | ||
|
|
173129014a | ||
|
|
8d4baa6d31 | ||
|
|
20463ce653 |
29
.github/scripts/decrypt-secret.ps1
vendored
Normal file
29
.github/scripts/decrypt-secret.ps1
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
param (
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string] $filename,
|
||||||
|
[string] $output
|
||||||
|
)
|
||||||
|
|
||||||
|
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path
|
||||||
|
$rootPath = $env:GITHUB_WORKSPACE
|
||||||
|
|
||||||
|
$secretInputPath = $rootPath + "/.github/secrets"
|
||||||
|
$input = $secretInputPath + "/" + $filename
|
||||||
|
|
||||||
|
$passphrase = $env:DECRYPT_FILE_PASSWORD
|
||||||
|
$secretOutputPath = $homePath + "/secrets"
|
||||||
|
|
||||||
|
if ([string]::IsNullOrEmpty($output)) {
|
||||||
|
if ($filename.EndsWith(".gpg")) {
|
||||||
|
$output = $secretOutputPath + "/" + $filename.TrimEnd(".gpg")
|
||||||
|
} else {
|
||||||
|
$output = $secretOutputPath + "/" + $filename + ".plaintext"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(Test-Path -Path $secretOutputPath))
|
||||||
|
{
|
||||||
|
New-Item -ItemType Directory -Path $secretOutputPath
|
||||||
|
}
|
||||||
|
|
||||||
|
gpg --quiet --batch --yes --decrypt --passphrase="$passphrase" --output $output $input
|
||||||
5
.github/scripts/load-version.ps1
vendored
Normal file
5
.github/scripts/load-version.ps1
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
$rootPath = $env:GITHUB_WORKSPACE;
|
||||||
|
$packageVersion = (Get-Content -Raw -Path $rootPath\src\package.json | ConvertFrom-Json).version;
|
||||||
|
|
||||||
|
Write-Output "Setting package version to $packageVersion";
|
||||||
|
Write-Output "PACKAGE_VERSION=$packageVersion" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append;
|
||||||
7
.github/scripts/macos/decrypt-secrets.ps1
vendored
Normal file
7
.github/scripts/macos/decrypt-secrets.ps1
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
$rootPath = $env:GITHUB_WORKSPACE;
|
||||||
|
|
||||||
|
$decryptSecretPath = $($rootPath + "/.github/scripts/decrypt-secret.ps1");
|
||||||
|
|
||||||
|
Invoke-Expression "& `"$decryptSecretPath`" -filename devid-app-cert.p12.gpg"
|
||||||
|
Invoke-Expression "& `"$decryptSecretPath`" -filename devid-installer-cert.p12.gpg"
|
||||||
|
Invoke-Expression "& `"$decryptSecretPath`" -filename macdev-cert.p12.gpg"
|
||||||
15
.github/scripts/macos/setup-keychain.ps1
vendored
Normal file
15
.github/scripts/macos/setup-keychain.ps1
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path;
|
||||||
|
$secretsPath = $homePath + "/secrets"
|
||||||
|
|
||||||
|
$devidAppCertPath = $($secretsPath + "/devid-app-cert.p12");
|
||||||
|
$devidInstallerCertPath = $($secretsPath + "/devid-installer-cert.p12");
|
||||||
|
$macdevCertPath = $($secretsPath + "/macdev-cert.p12");
|
||||||
|
|
||||||
|
security create-keychain -p $env:KEYCHAIN_PASSWORD build.keychain
|
||||||
|
security default-keychain -s build.keychain
|
||||||
|
security unlock-keychain -p $env:KEYCHAIN_PASSWORD build.keychain
|
||||||
|
security set-keychain-settings -lut 1200 build.keychain
|
||||||
|
security import $devidAppCertPath -k build.keychain -P $env:DEVID_CERT_PASSWORD -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
|
||||||
|
security import $devidInstallerCertPath -k build.keychain -P $env:DEVID_CERT_PASSWORD -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
|
||||||
|
security import $macdevCertPath -k build.keychain -P $env:MACDEV_CERT_PASSWORD -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
|
||||||
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $env:KEYCHAIN_PASSWORD build.keychain
|
||||||
BIN
.github/secrets/devid-app-cert.p12.gpg
vendored
Normal file
BIN
.github/secrets/devid-app-cert.p12.gpg
vendored
Normal file
Binary file not shown.
BIN
.github/secrets/devid-installer-cert.p12.gpg
vendored
Normal file
BIN
.github/secrets/devid-installer-cert.p12.gpg
vendored
Normal file
Binary file not shown.
BIN
.github/secrets/macdev-cert.p12.gpg
vendored
Normal file
BIN
.github/secrets/macdev-cert.p12.gpg
vendored
Normal file
Binary file not shown.
414
.github/workflows/build.yml
vendored
Normal file
414
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,414 @@
|
|||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches-ignore:
|
||||||
|
- 'l10n_master'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cloc:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||||
|
|
||||||
|
- name: Set up cloc
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt -y install cloc
|
||||||
|
- name: Print lines of code
|
||||||
|
run: cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git
|
||||||
|
|
||||||
|
|
||||||
|
setup:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
package_version: ${{ steps.get_version.outputs.package_version }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||||
|
|
||||||
|
- name: Get Package Version
|
||||||
|
id: get_version
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$env:pkgVersion = (Get-Content -Raw -Path ./src/package.json | ConvertFrom-Json).version
|
||||||
|
echo "::set-output name=PACKAGE_VERSION::$env:pkgVersion"
|
||||||
|
|
||||||
|
|
||||||
|
cli:
|
||||||
|
runs-on: windows-latest
|
||||||
|
needs: setup
|
||||||
|
env:
|
||||||
|
PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||||
|
|
||||||
|
- name: Setup Windows builder
|
||||||
|
run: |
|
||||||
|
choco install checksum --no-progress
|
||||||
|
choco install reshack --no-progress
|
||||||
|
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
||||||
|
with:
|
||||||
|
node-version: '14.x'
|
||||||
|
|
||||||
|
- name: Update NPM
|
||||||
|
run: |
|
||||||
|
npm install -g npm@7
|
||||||
|
npm install -g node-gyp
|
||||||
|
node-gyp install $(node -v)
|
||||||
|
|
||||||
|
- name: Setting WIN_PKG
|
||||||
|
run: |
|
||||||
|
echo "WIN_PKG=$env:WIN_PKG" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||||
|
echo "version: $env:pkgVersion"
|
||||||
|
env:
|
||||||
|
WIN_PKG: C:\Users\runneradmin\.pkg-cache\v3.0\fetched-v14.16.1-win-x64
|
||||||
|
|
||||||
|
- name: get pkg-fetch
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
cd $HOME
|
||||||
|
$fetchedUrl = "https://github.com/vercel/pkg-fetch/releases/download/v3.0/node-v14.16.1-win-x64"
|
||||||
|
|
||||||
|
New-Item -ItemType directory -Path ./.pkg-cache
|
||||||
|
New-Item -ItemType directory -Path ./.pkg-cache/v3.0
|
||||||
|
Invoke-RestMethod -Uri $fetchedUrl -OutFile "./.pkg-cache/v3.0/fetched-v14.16.1-win-x64"
|
||||||
|
env:
|
||||||
|
WIN_PKG: C:\Users\runneradmin\.pkg-cache\v3.0\fetched-v14.16.1-win-x64
|
||||||
|
|
||||||
|
- name: Keytar
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$keytarVersion = (Get-Content -Raw -Path ./src/package.json | ConvertFrom-Json).dependencies.keytar
|
||||||
|
$nodeModVersion = node -e "console.log(process.config.variables.node_module_version)"
|
||||||
|
$keytarTar = "keytar-v${keytarVersion}-node-v${nodeModVersion}-{0}-x64.tar"
|
||||||
|
$keytarTarGz = "${keytarTar}.gz"
|
||||||
|
$keytarUrl = "https://github.com/atom/node-keytar/releases/download/v${keytarVersion}/${keytarTarGz}"
|
||||||
|
|
||||||
|
New-Item -ItemType directory -Path ./keytar/macos | Out-Null
|
||||||
|
New-Item -ItemType directory -Path ./keytar/linux | Out-Null
|
||||||
|
New-Item -ItemType directory -Path ./keytar/windows | Out-Null
|
||||||
|
|
||||||
|
Invoke-RestMethod -Uri $($keytarUrl -f "darwin") -OutFile "./keytar/macos/$($keytarTarGz -f "darwin")"
|
||||||
|
Invoke-RestMethod -Uri $($keytarUrl -f "linux") -OutFile "./keytar/linux/$($keytarTarGz -f "linux")"
|
||||||
|
Invoke-RestMethod -Uri $($keytarUrl -f "win32") -OutFile "./keytar/windows/$($keytarTarGz -f "win32")"
|
||||||
|
|
||||||
|
7z e "./keytar/macos/$($keytarTarGz -f "darwin")" -o"./keytar/macos"
|
||||||
|
7z e "./keytar/linux/$($keytarTarGz -f "linux")" -o"./keytar/linux"
|
||||||
|
7z e "./keytar/windows/$($keytarTarGz -f "win32")" -o"./keytar/windows"
|
||||||
|
|
||||||
|
7z e "./keytar/macos/$($keytarTar -f "darwin")" -o"./keytar/macos"
|
||||||
|
7z e "./keytar/linux/$($keytarTar -f "linux")" -o"./keytar/linux"
|
||||||
|
7z e "./keytar/windows/$($keytarTar -f "win32")" -o"./keytar/windows"
|
||||||
|
|
||||||
|
- name: Setup Version Info
|
||||||
|
shell: pwsh
|
||||||
|
run: ./scripts/make-versioninfo.ps1
|
||||||
|
|
||||||
|
- name: Resource Hacker
|
||||||
|
shell: cmd
|
||||||
|
run: |
|
||||||
|
set PATH=%PATH%;C:\Program Files (x86)\Resource Hacker
|
||||||
|
ResourceHacker -open %WIN_PKG% -save %WIN_PKG% -action delete -mask ICONGROUP,1,
|
||||||
|
ResourceHacker -open version-info.rc -save version-info.res -action compile
|
||||||
|
ResourceHacker -open %WIN_PKG% -save %WIN_PKG% -action addoverwrite -resource version-info.res
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Package CLI
|
||||||
|
run: npm run dist:cli
|
||||||
|
|
||||||
|
- name: Zip
|
||||||
|
shell: cmd
|
||||||
|
run: |
|
||||||
|
7z a ./dist-cli/bwdc-windows-%PACKAGE_VERSION%.zip ./dist-cli/windows/bwdc.exe ./keytar/windows/keytar.node
|
||||||
|
7z a ./dist-cli/bwdc-macos-%PACKAGE_VERSION%.zip ./dist-cli/macos/bwdc ./keytar/macos/keytar.node
|
||||||
|
7z a ./dist-cli/bwdc-linux-%PACKAGE_VERSION%.zip ./dist-cli/linux/bwdc ./keytar/linux/keytar.node
|
||||||
|
|
||||||
|
- name: Version Test
|
||||||
|
run: |
|
||||||
|
Expand-Archive -Path "./dist-cli/bwdc-windows-${env:PACKAGE_VERSION}.zip" -DestinationPath "./test/windows"
|
||||||
|
$testVersion = Invoke-Expression '& ./test/windows/bwdc.exe -v'
|
||||||
|
echo "version: $env:PACKAGE_VERSION"
|
||||||
|
echo "testVersion: $testVersion"
|
||||||
|
if($testVersion -ne $env:PACKAGE_VERSION) {
|
||||||
|
Throw "Version test failed."
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Create checksums
|
||||||
|
run: |
|
||||||
|
checksum -f="./dist-cli/bwdc-windows-${env:PACKAGE_VERSION}.zip" `
|
||||||
|
-t sha256 | Out-File ./dist-cli/bwdc-windows-sha256-${env:PACKAGE_VERSION}.txt
|
||||||
|
checksum -f="./dist-cli/bwdc-macos-${env:PACKAGE_VERSION}.zip" `
|
||||||
|
-t sha256 | Out-File ./dist-cli/bwdc-macos-sha256-${env:PACKAGE_VERSION}.txt
|
||||||
|
checksum -f="./dist-cli/bwdc-linux-${env:PACKAGE_VERSION}.zip" `
|
||||||
|
-t sha256 | Out-File ./dist-cli/bwdc-linux-sha256-${env:PACKAGE_VERSION}.txt
|
||||||
|
|
||||||
|
- name: Upload windows zip to GitHub
|
||||||
|
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
|
||||||
|
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
|
||||||
|
with:
|
||||||
|
name: bwdc-windows-${{ env.PACKAGE_VERSION }}.zip
|
||||||
|
path: ./dist-cli/bwdc-windows-${{ env.PACKAGE_VERSION }}.zip
|
||||||
|
|
||||||
|
- name: Upload mac zip to GitHub
|
||||||
|
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
|
||||||
|
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
|
||||||
|
with:
|
||||||
|
name: bwdc-macos-${{ env.PACKAGE_VERSION }}.zip
|
||||||
|
path: ./dist-cli/bwdc-macos-${{ env.PACKAGE_VERSION }}.zip
|
||||||
|
|
||||||
|
- name: Upload linux zip to GitHub
|
||||||
|
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
|
||||||
|
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
|
||||||
|
with:
|
||||||
|
name: bwdc-linux-${{ env.PACKAGE_VERSION }}.zip
|
||||||
|
path: ./dist-cli/bwdc-linux-${{ env.PACKAGE_VERSION }}.zip
|
||||||
|
|
||||||
|
- name: Upload windows checksum to GitHub
|
||||||
|
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
|
||||||
|
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
|
||||||
|
with:
|
||||||
|
name: bwdc-windows-sha256-${{ env.PACKAGE_VERSION }}.txt
|
||||||
|
path: ./dist-cli/bwdc-windows-sha256-${{ env.PACKAGE_VERSION }}.txt
|
||||||
|
|
||||||
|
- name: Upload mac checksum to GitHub
|
||||||
|
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
|
||||||
|
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
|
||||||
|
with:
|
||||||
|
name: bwdc-macos-sha256-${{ env.PACKAGE_VERSION }}.txt
|
||||||
|
path: ./dist-cli/bwdc-macos-sha256-${{ env.PACKAGE_VERSION }}.txt
|
||||||
|
|
||||||
|
- name: Upload linux checksum to GitHub
|
||||||
|
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
|
||||||
|
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
|
||||||
|
with:
|
||||||
|
name: bwdc-linux-sha256-${{ env.PACKAGE_VERSION }}.txt
|
||||||
|
path: ./dist-cli/bwdc-linux-sha256-${{ env.PACKAGE_VERSION }}.txt
|
||||||
|
|
||||||
|
|
||||||
|
windows_gui:
|
||||||
|
runs-on: windows-latest
|
||||||
|
needs: setup
|
||||||
|
env:
|
||||||
|
PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||||
|
steps:
|
||||||
|
- name: Set up dotnet
|
||||||
|
uses: actions/setup-dotnet@a71d1eb2c86af85faa8c772c03fb365e377e45ea
|
||||||
|
with:
|
||||||
|
dotnet-version: "3.1.x"
|
||||||
|
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
||||||
|
with:
|
||||||
|
node-version: '14.x'
|
||||||
|
|
||||||
|
- name: Update NPM
|
||||||
|
run: |
|
||||||
|
npm install -g npm@7
|
||||||
|
npm install -g node-gyp
|
||||||
|
node-gyp install $(node -v)
|
||||||
|
|
||||||
|
- name: Set Node options
|
||||||
|
run: echo "NODE_OPTIONS=--max_old_space_size=4096" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||||
|
shell: pwsh
|
||||||
|
|
||||||
|
- name: Print environment
|
||||||
|
run: |
|
||||||
|
node --version
|
||||||
|
npm --version
|
||||||
|
dotnet --version
|
||||||
|
|
||||||
|
- name: Install AST
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
cd $HOME
|
||||||
|
|
||||||
|
git clone https://github.com/vcsjones/AzureSignTool.git
|
||||||
|
cd AzureSignTool
|
||||||
|
$latest_head = $(git rev-parse HEAD)[0..9] -join ""
|
||||||
|
$latest_version = "0.0.0-g$latest_head"
|
||||||
|
|
||||||
|
Write-Host "--------"
|
||||||
|
Write-Host "git commit - $(git rev-parse HEAD)"
|
||||||
|
Write-Host "latest_head - $latest_head"
|
||||||
|
Write-Host "PACKAGE VERSION TO BUILD - $latest_version"
|
||||||
|
Write-Host "--------"
|
||||||
|
|
||||||
|
dotnet restore
|
||||||
|
dotnet pack --output ./nupkg
|
||||||
|
dotnet tool install --global --ignore-failed-sources --add-source ./nupkg --version $latest_version azuresigntool
|
||||||
|
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||||
|
|
||||||
|
- name: Install Node dependencies
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Run linter
|
||||||
|
run: npm run lint
|
||||||
|
|
||||||
|
- name: Build & Sign
|
||||||
|
run: npm run dist:win
|
||||||
|
env:
|
||||||
|
ELECTRON_BUILDER_SIGN: 1
|
||||||
|
SIGNING_VAULT_URL: ${{ secrets.SIGNING_VAULT_URL }}
|
||||||
|
SIGNING_CLIENT_ID: ${{ secrets.SIGNING_CLIENT_ID }}
|
||||||
|
SIGNING_TENANT_ID: ${{ secrets.SIGNING_TENANT_ID }}
|
||||||
|
SIGNING_CLIENT_SECRET: ${{ secrets.SIGNING_CLIENT_SECRET }}
|
||||||
|
SIGNING_CERT_NAME: ${{ secrets.SIGNING_CERT_NAME }}
|
||||||
|
|
||||||
|
- name: List Dist
|
||||||
|
run: dir ./dist
|
||||||
|
|
||||||
|
- name: Publish Portable Exe to GitHub
|
||||||
|
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
|
||||||
|
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
|
||||||
|
with:
|
||||||
|
name: Bitwarden-Connector-Portable-${{ env.PACKAGE_VERSION }}.exe
|
||||||
|
path: ./dist/Bitwarden-Connector-Portable-${{ env.PACKAGE_VERSION }}.exe
|
||||||
|
|
||||||
|
- name: Publish Installer Exe to GitHub
|
||||||
|
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
|
||||||
|
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
|
||||||
|
with:
|
||||||
|
name: Bitwarden-Connector-Installer-${{ env.PACKAGE_VERSION }}.exe
|
||||||
|
path: ./dist/Bitwarden-Connector-Installer-${{ env.PACKAGE_VERSION }}.exe
|
||||||
|
|
||||||
|
|
||||||
|
linux:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: setup
|
||||||
|
env:
|
||||||
|
PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||||
|
steps:
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
||||||
|
with:
|
||||||
|
node-version: '14.x'
|
||||||
|
|
||||||
|
- name: Update NPM
|
||||||
|
run: |
|
||||||
|
npm install -g npm@7
|
||||||
|
npm install -g node-gyp
|
||||||
|
node-gyp install $(node -v)
|
||||||
|
|
||||||
|
- name: Set Node options
|
||||||
|
run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Set up environment
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get -y install pkg-config libxss-dev libsecret-1-dev
|
||||||
|
sudo apt-get -y install rpm
|
||||||
|
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||||
|
|
||||||
|
- name: npm install
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: npm rebuild
|
||||||
|
run: npm run rebuild
|
||||||
|
|
||||||
|
- name: npm package
|
||||||
|
run: npm run dist:lin
|
||||||
|
|
||||||
|
- name: Publish AppImage
|
||||||
|
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
|
||||||
|
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
|
||||||
|
with:
|
||||||
|
name: Bitwarden-Connector-${{ env.PACKAGE_VERSION }}-x86_64.AppImage
|
||||||
|
path: ./dist/Bitwarden-Connector-${{ env.PACKAGE_VERSION }}-x86_64.AppImage
|
||||||
|
|
||||||
|
|
||||||
|
macos:
|
||||||
|
runs-on: macos-latest
|
||||||
|
needs: setup
|
||||||
|
env:
|
||||||
|
PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||||
|
steps:
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
||||||
|
with:
|
||||||
|
node-version: '14.x'
|
||||||
|
|
||||||
|
- name: Update NPM
|
||||||
|
run: |
|
||||||
|
npm install -g npm@7
|
||||||
|
npm install -g node-gyp
|
||||||
|
node-gyp install $(node -v)
|
||||||
|
|
||||||
|
- name: Set Node options
|
||||||
|
run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Print environment
|
||||||
|
run: |
|
||||||
|
node --version
|
||||||
|
npm --version
|
||||||
|
Write-Output "GitHub ref: $env:GITHUB_REF"
|
||||||
|
Write-Output "GitHub event: $env:GITHUB_EVENT"
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
GITHUB_REF: ${{ github.ref }}
|
||||||
|
GITHUB_EVENT: ${{ github.event_name }}
|
||||||
|
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||||
|
|
||||||
|
- name: Decrypt secrets
|
||||||
|
run: ./.github/scripts/macos/decrypt-secrets.ps1
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Set up keychain
|
||||||
|
run: ./.github/scripts/macos/setup-keychain.ps1
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||||
|
DEVID_CERT_PASSWORD: ${{ secrets.DEVID_CERT_PASSWORD }}
|
||||||
|
MACDEV_CERT_PASSWORD: ${{ secrets.MACDEV_CERT_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Load package version
|
||||||
|
run: ./.github/scripts/load-version.ps1
|
||||||
|
shell: pwsh
|
||||||
|
|
||||||
|
- name: Install Node dependencies
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Run linter
|
||||||
|
run: npm run lint
|
||||||
|
|
||||||
|
- name: Build application (dev)
|
||||||
|
if: github.ref != 'refs/heads/master'
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Build application (dist)
|
||||||
|
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
|
||||||
|
run: npm run dist:mac
|
||||||
|
env:
|
||||||
|
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
|
||||||
|
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Upload .zip artifact
|
||||||
|
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
|
||||||
|
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
|
||||||
|
with:
|
||||||
|
name: Bitwarden-Connector-${{ env.PACKAGE_VERSION }}-mac.zip
|
||||||
|
path: ./dist/Bitwarden-Connector-${{ env.PACKAGE_VERSION }}-mac.zip
|
||||||
|
|
||||||
|
- name: Upload .dmg artifact
|
||||||
|
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
|
||||||
|
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
|
||||||
|
with:
|
||||||
|
name: Bitwarden-Connector-${{ env.PACKAGE_VERSION }}.dmg
|
||||||
|
path: ./dist/Bitwarden-Connector-${{ env.PACKAGE_VERSION }}.dmg
|
||||||
425
.github/workflows/release.yml
vendored
Normal file
425
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,425 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
release_tag_name_input:
|
||||||
|
description: "Release Tag Name <X.X.X>"
|
||||||
|
required: true
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
setup:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
package_version: ${{ steps.create_tags.outputs.package_version }}
|
||||||
|
tag_version: ${{ steps.create_tags.outputs.tag_version }}
|
||||||
|
release_upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
steps:
|
||||||
|
- name: Branch check
|
||||||
|
run: |
|
||||||
|
if [[ "$GITHUB_REF" != "refs/heads/rc" ]]; then
|
||||||
|
echo "==================================="
|
||||||
|
echo "[!] Can only release from rc branch"
|
||||||
|
echo "==================================="
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||||
|
|
||||||
|
- name: Create Release Vars
|
||||||
|
id: create_tags
|
||||||
|
run: |
|
||||||
|
case "${RELEASE_TAG_NAME_INPUT:0:1}" in
|
||||||
|
v)
|
||||||
|
echo "RELEASE_NAME=${RELEASE_TAG_NAME_INPUT:1}" >> $GITHUB_ENV
|
||||||
|
echo "RELEASE_TAG_NAME=$RELEASE_TAG_NAME_INPUT" >> $GITHUB_ENV
|
||||||
|
echo "::set-output name=package_version::${RELEASE_TAG_NAME_INPUT:1}"
|
||||||
|
echo "::set-output name=tag_version::$RELEASE_TAG_NAME_INPUT"
|
||||||
|
;;
|
||||||
|
[0-9])
|
||||||
|
echo "RELEASE_NAME=$RELEASE_TAG_NAME_INPUT" >> $GITHUB_ENV
|
||||||
|
echo "RELEASE_TAG_NAME=v$RELEASE_TAG_NAME_INPUT" >> $GITHUB_ENV
|
||||||
|
echo "::set-output name=package_version::$RELEASE_TAG_NAME_INPUT"
|
||||||
|
echo "::set-output name=tag_version::v$RELEASE_TAG_NAME_INPUT"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
env:
|
||||||
|
RELEASE_TAG_NAME_INPUT: ${{ github.event.inputs.release_tag_name_input }}
|
||||||
|
|
||||||
|
- name: Create Draft Release
|
||||||
|
id: create_release
|
||||||
|
uses: actions/create-release@0cb9c9b65d5d1901c1f53e5e66eaf4afd303e70e
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: ${{ env.RELEASE_TAG_NAME }}
|
||||||
|
release_name: ${{ env.RELEASE_NAME }}
|
||||||
|
draft: true
|
||||||
|
prerelease: false
|
||||||
|
|
||||||
|
|
||||||
|
cli:
|
||||||
|
runs-on: windows-latest
|
||||||
|
needs: setup
|
||||||
|
env:
|
||||||
|
PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||||
|
|
||||||
|
- name: Setup Windows builder
|
||||||
|
run: |
|
||||||
|
choco install checksum --no-progress
|
||||||
|
choco install reshack --no-progress
|
||||||
|
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
||||||
|
with:
|
||||||
|
node-version: '14.x'
|
||||||
|
|
||||||
|
- name: Update NPM
|
||||||
|
run: |
|
||||||
|
npm install -g npm@7
|
||||||
|
npm install -g node-gyp
|
||||||
|
node-gyp install $(node -v)
|
||||||
|
|
||||||
|
- name: Set VER_INFO
|
||||||
|
run: |
|
||||||
|
echo "WIN_PKG=$env:WIN_PKG" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||||
|
env:
|
||||||
|
WIN_PKG: C:\Users\runneradmin\.pkg-cache\v3.0\fetched-v14.16.1-win-x64
|
||||||
|
|
||||||
|
- name: get pkg-fetch
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
cd $HOME
|
||||||
|
$fetchedUrl = "https://github.com/vercel/pkg-fetch/releases/download/v3.0/node-v14.16.1-win-x64"
|
||||||
|
|
||||||
|
New-Item -ItemType directory -Path ./.pkg-cache
|
||||||
|
New-Item -ItemType directory -Path ./.pkg-cache/v3.0
|
||||||
|
Invoke-RestMethod -Uri $fetchedUrl -OutFile "./.pkg-cache/v3.0/fetched-v14.16.1-win-x64"
|
||||||
|
env:
|
||||||
|
WIN_PKG: C:\Users\runneradmin\.pkg-cache\v3.0\fetched-v14.16.1-win-x64
|
||||||
|
|
||||||
|
- name: Keytar
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$keytarVersion = (Get-Content -Raw -Path ./src/package.json | ConvertFrom-Json).dependencies.keytar
|
||||||
|
$nodeModVersion = node -e "console.log(process.config.variables.node_module_version)"
|
||||||
|
$keytarTar = "keytar-v${keytarVersion}-node-v${nodeModVersion}-{0}-x64.tar"
|
||||||
|
$keytarTarGz = "${keytarTar}.gz"
|
||||||
|
$keytarUrl = "https://github.com/atom/node-keytar/releases/download/v${keytarVersion}/${keytarTarGz}"
|
||||||
|
|
||||||
|
New-Item -ItemType directory -Path ./keytar/macos | Out-Null
|
||||||
|
New-Item -ItemType directory -Path ./keytar/linux | Out-Null
|
||||||
|
New-Item -ItemType directory -Path ./keytar/windows | Out-Null
|
||||||
|
|
||||||
|
Invoke-RestMethod -Uri $($keytarUrl -f "darwin") -OutFile "./keytar/macos/$($keytarTarGz -f "darwin")"
|
||||||
|
Invoke-RestMethod -Uri $($keytarUrl -f "linux") -OutFile "./keytar/linux/$($keytarTarGz -f "linux")"
|
||||||
|
Invoke-RestMethod -Uri $($keytarUrl -f "win32") -OutFile "./keytar/windows/$($keytarTarGz -f "win32")"
|
||||||
|
|
||||||
|
7z e "./keytar/macos/$($keytarTarGz -f "darwin")" -o"./keytar/macos"
|
||||||
|
7z e "./keytar/linux/$($keytarTarGz -f "linux")" -o"./keytar/linux"
|
||||||
|
7z e "./keytar/windows/$($keytarTarGz -f "win32")" -o"./keytar/windows"
|
||||||
|
|
||||||
|
7z e "./keytar/macos/$($keytarTar -f "darwin")" -o"./keytar/macos"
|
||||||
|
7z e "./keytar/linux/$($keytarTar -f "linux")" -o"./keytar/linux"
|
||||||
|
7z e "./keytar/windows/$($keytarTar -f "win32")" -o"./keytar/windows"
|
||||||
|
|
||||||
|
- name: Setup Version Info
|
||||||
|
shell: pwsh
|
||||||
|
run: ./scripts/make-versioninfo.ps1
|
||||||
|
|
||||||
|
- name: Resource Hacker
|
||||||
|
shell: cmd
|
||||||
|
run: |
|
||||||
|
set PATH=%PATH%;C:\Program Files (x86)\Resource Hacker
|
||||||
|
ResourceHacker -open %WIN_PKG% -save %WIN_PKG% -action delete -mask ICONGROUP,1,
|
||||||
|
ResourceHacker -open version-info.rc -save version-info.res -action compile
|
||||||
|
ResourceHacker -open %WIN_PKG% -save %WIN_PKG% -action addoverwrite -resource version-info.res
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Package CLI
|
||||||
|
run: npm run dist:cli
|
||||||
|
|
||||||
|
- name: Zip
|
||||||
|
shell: cmd
|
||||||
|
run: |
|
||||||
|
7z a ./dist-cli/bwdc-windows-%PACKAGE_VERSION%.zip ./dist-cli/windows/bwdc.exe ./keytar/windows/keytar.node
|
||||||
|
7z a ./dist-cli/bwdc-macos-%PACKAGE_VERSION%.zip ./dist-cli/macos/bwdc ./keytar/macos/keytar.node
|
||||||
|
7z a ./dist-cli/bwdc-linux-%PACKAGE_VERSION%.zip ./dist-cli/linux/bwdc ./keytar/linux/keytar.node
|
||||||
|
|
||||||
|
- name: Version Test
|
||||||
|
run: |
|
||||||
|
Expand-Archive -Path "./dist-cli/bwdc-windows-${env:PACKAGE_VERSION}.zip" -DestinationPath "./test/windows"
|
||||||
|
$testVersion = Invoke-Expression '& ./test/windows/bwdc.exe -v'
|
||||||
|
echo "version: $env:PACKAGE_VERSION"
|
||||||
|
echo "testVersion: $testVersion"
|
||||||
|
if($testVersion -ne $env:PACKAGE_VERSION) {
|
||||||
|
Throw "Version test failed."
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Create checksums
|
||||||
|
run: |
|
||||||
|
checksum -f="./dist-cli/bwdc-windows-${env:PACKAGE_VERSION}.zip" `
|
||||||
|
-t sha256 | Out-File ./dist-cli/bwdc-windows-sha256-${env:PACKAGE_VERSION}.txt
|
||||||
|
checksum -f="./dist-cli/bwdc-macos-${env:PACKAGE_VERSION}.zip" `
|
||||||
|
-t sha256 | Out-File ./dist-cli/bwdc-macos-sha256-${env:PACKAGE_VERSION}.txt
|
||||||
|
checksum -f="./dist-cli/bwdc-linux-${env:PACKAGE_VERSION}.zip" `
|
||||||
|
-t sha256 | Out-File ./dist-cli/bwdc-linux-sha256-${env:PACKAGE_VERSION}.txt
|
||||||
|
|
||||||
|
- name: upload windows zip release asset
|
||||||
|
uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ needs.setup.outputs.release_upload_url }}
|
||||||
|
asset_path: ./dist-cli/bwdc-windows-${{ env.PACKAGE_VERSION }}.zip
|
||||||
|
asset_name: bwdc-windows-${{ env.PACKAGE_VERSION }}.zip
|
||||||
|
asset_content_type: application/zip
|
||||||
|
|
||||||
|
- name: upload macos zip release asset
|
||||||
|
uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ needs.setup.outputs.release_upload_url }}
|
||||||
|
asset_path: ./dist-cli/bwdc-macos-${{ env.PACKAGE_VERSION }}.zip
|
||||||
|
asset_name: bwdc-macos-${{ env.PACKAGE_VERSION }}.zip
|
||||||
|
asset_content_type: application/zip
|
||||||
|
|
||||||
|
- name: upload linux zip release asset
|
||||||
|
uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ needs.setup.outputs.release_upload_url }}
|
||||||
|
asset_path: ./dist-cli/bwdc-linux-${{ env.PACKAGE_VERSION }}.zip
|
||||||
|
asset_name: bwdc-linux-${{ env.PACKAGE_VERSION }}.zip
|
||||||
|
asset_content_type: application/zip
|
||||||
|
|
||||||
|
- name: upload windows checksum release asset
|
||||||
|
uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ needs.setup.outputs.release_upload_url }}
|
||||||
|
asset_path: ./dist-cli/bwdc-windows-sha256-${{ env.PACKAGE_VERSION }}.txt
|
||||||
|
asset_name: bwdc-windows-sha256-${{ env.PACKAGE_VERSION }}.txt
|
||||||
|
asset_content_type: text/plain
|
||||||
|
|
||||||
|
- name: upload macos checksum release asset
|
||||||
|
uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ needs.setup.outputs.release_upload_url }}
|
||||||
|
asset_path: ./dist-cli/bwdc-macos-sha256-${{ env.PACKAGE_VERSION }}.txt
|
||||||
|
asset_name: bwdc-macos-sha256-${{ env.PACKAGE_VERSION }}.txt
|
||||||
|
asset_content_type: text/plain
|
||||||
|
|
||||||
|
- name: upload linux checksum release asset
|
||||||
|
uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ needs.setup.outputs.release_upload_url }}
|
||||||
|
asset_path: ./dist-cli/bwdc-linux-sha256-${{ env.PACKAGE_VERSION }}.txt
|
||||||
|
asset_name: bwdc-linux-sha256-${{ env.PACKAGE_VERSION }}.txt
|
||||||
|
asset_content_type: text/plain
|
||||||
|
|
||||||
|
|
||||||
|
windows-gui:
|
||||||
|
runs-on: windows-latest
|
||||||
|
needs: setup
|
||||||
|
env:
|
||||||
|
PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||||
|
steps:
|
||||||
|
- name: Set up dotnet
|
||||||
|
uses: actions/setup-dotnet@a71d1eb2c86af85faa8c772c03fb365e377e45ea
|
||||||
|
with:
|
||||||
|
dotnet-version: "3.1.x"
|
||||||
|
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
||||||
|
with:
|
||||||
|
node-version: '14.x'
|
||||||
|
|
||||||
|
- name: Update NPM
|
||||||
|
run: |
|
||||||
|
npm install -g npm@7
|
||||||
|
npm install -g node-gyp
|
||||||
|
node-gyp install $(node -v)
|
||||||
|
|
||||||
|
- name: Set Node options
|
||||||
|
run: echo "NODE_OPTIONS=--max_old_space_size=4096" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||||
|
shell: pwsh
|
||||||
|
|
||||||
|
- name: Print environment
|
||||||
|
run: |
|
||||||
|
node --version
|
||||||
|
npm --version
|
||||||
|
dotnet --version
|
||||||
|
|
||||||
|
- name: Install AST
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
cd $HOME
|
||||||
|
git clone https://github.com/vcsjones/AzureSignTool.git
|
||||||
|
cd AzureSignTool
|
||||||
|
$latest_head = $(git rev-parse HEAD)[0..9] -join ""
|
||||||
|
$latest_version = "0.0.0-g$latest_head"
|
||||||
|
Write-Host "--------"
|
||||||
|
Write-Host "git commit - $(git rev-parse HEAD)"
|
||||||
|
Write-Host "latest_head - $latest_head"
|
||||||
|
Write-Host "PACKAGE VERSION TO BUILD - $latest_version"
|
||||||
|
Write-Host "--------"
|
||||||
|
dotnet restore
|
||||||
|
dotnet pack --output ./nupkg
|
||||||
|
dotnet tool install --global --ignore-failed-sources --add-source ./nupkg --version $latest_version azuresigntool
|
||||||
|
cd $HOME
|
||||||
|
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||||
|
|
||||||
|
- name: Install Node dependencies
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Run linter
|
||||||
|
run: npm run lint
|
||||||
|
|
||||||
|
- name: npm rebuild
|
||||||
|
run: npm run rebuild
|
||||||
|
|
||||||
|
- name: Build & Sign
|
||||||
|
run: |
|
||||||
|
npm run publish:win
|
||||||
|
env:
|
||||||
|
ELECTRON_BUILDER_SIGN: 1
|
||||||
|
SIGNING_VAULT_URL: ${{ secrets.SIGNING_VAULT_URL }}
|
||||||
|
SIGNING_CLIENT_ID: ${{ secrets.SIGNING_CLIENT_ID }}
|
||||||
|
SIGNING_TENANT_ID: ${{ secrets.SIGNING_TENANT_ID }}
|
||||||
|
SIGNING_CLIENT_SECRET: ${{ secrets.SIGNING_CLIENT_SECRET }}
|
||||||
|
SIGNING_CERT_NAME: ${{ secrets.SIGNING_CERT_NAME }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
|
||||||
|
linux:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: setup
|
||||||
|
env:
|
||||||
|
PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||||
|
steps:
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
||||||
|
with:
|
||||||
|
node-version: '14.x'
|
||||||
|
|
||||||
|
- name: Update NPM
|
||||||
|
run: |
|
||||||
|
npm install -g npm@7
|
||||||
|
npm install -g node-gyp
|
||||||
|
node-gyp install $(node -v)
|
||||||
|
|
||||||
|
- name: Set Node options
|
||||||
|
run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Set up environment
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get -y install pkg-config libxss-dev libsecret-1-dev
|
||||||
|
sudo apt-get -y install rpm
|
||||||
|
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||||
|
|
||||||
|
- name: Set PACKAGE_VERSION
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$env:pkgVersion = (Get-Content -Raw -Path ./src/package.json | ConvertFrom-Json).version
|
||||||
|
echo "PACKAGE_VERSION=$env:pkgVersion" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||||
|
echo "version: $env:pkgVersion"
|
||||||
|
|
||||||
|
- name: npm install
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: npm rebuild
|
||||||
|
run: npm run rebuild
|
||||||
|
|
||||||
|
- name: npm package
|
||||||
|
run: npm run publish:lin
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
|
||||||
|
macos:
|
||||||
|
runs-on: macos-latest
|
||||||
|
needs: setup
|
||||||
|
env:
|
||||||
|
PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||||
|
steps:
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
||||||
|
with:
|
||||||
|
node-version: '14.x'
|
||||||
|
|
||||||
|
- name: Update NPM
|
||||||
|
run: |
|
||||||
|
npm install -g npm@7
|
||||||
|
npm install -g node-gyp
|
||||||
|
node-gyp install $(node -v)
|
||||||
|
|
||||||
|
- name: Set Node options
|
||||||
|
run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Print environment
|
||||||
|
run: |
|
||||||
|
node --version
|
||||||
|
npm --version
|
||||||
|
Write-Output "GitHub ref: $env:GITHUB_REF"
|
||||||
|
Write-Output "GitHub event: $env:GITHUB_EVENT"
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
GITHUB_REF: ${{ github.ref }}
|
||||||
|
GITHUB_EVENT: ${{ github.event_name }}
|
||||||
|
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||||
|
|
||||||
|
- name: Decrypt secrets
|
||||||
|
run: ./.github/scripts/macos/decrypt-secrets.ps1
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Set up keychain
|
||||||
|
run: ./.github/scripts/macos/setup-keychain.ps1
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||||
|
DEVID_CERT_PASSWORD: ${{ secrets.DEVID_CERT_PASSWORD }}
|
||||||
|
MACDEV_CERT_PASSWORD: ${{ secrets.MACDEV_CERT_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Load package version
|
||||||
|
run: ./.github/scripts/load-version.ps1
|
||||||
|
shell: pwsh
|
||||||
|
|
||||||
|
- name: Install Node dependencies
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Run linter
|
||||||
|
run: npm run lint
|
||||||
|
|
||||||
|
- name: Build application (dist)
|
||||||
|
run: npm run publish:mac
|
||||||
|
env:
|
||||||
|
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
|
||||||
|
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,3 +14,4 @@ yarn-error.log
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
*.nupkg
|
*.nupkg
|
||||||
*.provisionprofile
|
*.provisionprofile
|
||||||
|
*.env
|
||||||
|
|||||||
13
.vscode/launch.json
vendored
13
.vscode/launch.json
vendored
@@ -23,7 +23,18 @@
|
|||||||
"port": 9223,
|
"port": 9223,
|
||||||
"webRoot": "${workspaceFolder}/build",
|
"webRoot": "${workspaceFolder}/build",
|
||||||
"sourceMaps": true
|
"sourceMaps": true
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug CLI",
|
||||||
|
"protocol": "inspector",
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"program": "${workspaceFolder}/build-cli/bwdc.js",
|
||||||
|
"args": [
|
||||||
|
"sync"
|
||||||
|
]
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"compounds": [
|
"compounds": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
[](https://ci.appveyor.com/project/bitwarden/directory-connector)
|

|
||||||
[](https://gitter.im/bitwarden/Lobby)
|
[](https://gitter.im/bitwarden/Lobby)
|
||||||
|
|
||||||
# Bitwarden Directory Connector
|
# Bitwarden Directory Connector
|
||||||
@@ -47,7 +47,7 @@ We provide detailed documentation and examples for using the Directory Connector
|
|||||||
|
|
||||||
**Requirements**
|
**Requirements**
|
||||||
|
|
||||||
- [Node.js](https://nodejs.org/)
|
- [Node.js](https://nodejs.org) v14
|
||||||
- Windows users: To compile the native node modules used in the app you will need the Visual C++ toolset, available through the standard Visual Studio installer (recommended) or by installing [`windows-build-tools`](https://github.com/felixrieseberg/windows-build-tools) through `npm`. See more at [Compiling native Addon modules](https://github.com/Microsoft/nodejs-guidelines/blob/master/windows-environment.md#compiling-native-addon-modules).
|
- Windows users: To compile the native node modules used in the app you will need the Visual C++ toolset, available through the standard Visual Studio installer (recommended) or by installing [`windows-build-tools`](https://github.com/felixrieseberg/windows-build-tools) through `npm`. See more at [Compiling native Addon modules](https://github.com/Microsoft/nodejs-guidelines/blob/master/windows-environment.md#compiling-native-addon-modules).
|
||||||
|
|
||||||
**Run the app**
|
**Run the app**
|
||||||
|
|||||||
162
appveyor.yml
162
appveyor.yml
@@ -1,162 +0,0 @@
|
|||||||
image:
|
|
||||||
- Visual Studio 2017
|
|
||||||
- Ubuntu1804
|
|
||||||
|
|
||||||
branches:
|
|
||||||
except:
|
|
||||||
- l10n_master
|
|
||||||
|
|
||||||
environment:
|
|
||||||
WIN_PKG: C:\Users\appveyor\.pkg-cache\v2.5\fetched-v10.4.1-win-x64
|
|
||||||
|
|
||||||
stack: node 10
|
|
||||||
|
|
||||||
init:
|
|
||||||
- ps: |
|
|
||||||
if($isWindows -and $env:DEBUG_RDP -eq "true") {
|
|
||||||
iex ((new-object net.webclient).DownloadString(`
|
|
||||||
'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
|
||||||
}
|
|
||||||
- sh: sudo apt-get update
|
|
||||||
- sh: sudo apt-get -y install pkg-config libxss-dev libsecret-1-dev rpm
|
|
||||||
- ps: |
|
|
||||||
if($isWindows) {
|
|
||||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
||||||
Install-Product node 10
|
|
||||||
$env:PATH = "C:\Program Files (x86)\Resource Hacker;${env:PATH}"
|
|
||||||
}
|
|
||||||
if($env:APPVEYOR_REPO_TAG -eq "true") {
|
|
||||||
$env:RELEASE_NAME = $env:APPVEYOR_REPO_TAG_NAME.TrimStart("v")
|
|
||||||
}
|
|
||||||
|
|
||||||
install:
|
|
||||||
- ps: |
|
|
||||||
$env:PACKAGE_VERSION = (Get-Content -Raw -Path .\src\package.json | ConvertFrom-Json).version
|
|
||||||
$env:PROD_DEPLOY = "false"
|
|
||||||
if($env:APPVEYOR_REPO_TAG -eq "true" -and $env:APPVEYOR_RE_BUILD -eq "True") {
|
|
||||||
$env:PROD_DEPLOY = "true"
|
|
||||||
echo "This is a production deployment."
|
|
||||||
}
|
|
||||||
if($isWindows) {
|
|
||||||
if(Test-Path -Path $env:WIN_PKG) {
|
|
||||||
$env:VER_INFO = "true"
|
|
||||||
}
|
|
||||||
choco install reshack --no-progress
|
|
||||||
choco install cloc --no-progress
|
|
||||||
choco install checksum --no-progress
|
|
||||||
cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git
|
|
||||||
.\make-versioninfo.ps1
|
|
||||||
}
|
|
||||||
- ps: |
|
|
||||||
if($isWindows) {
|
|
||||||
$keytarVersion = (Get-Content -Raw -Path .\src\package.json | ConvertFrom-Json).dependencies.keytar
|
|
||||||
$nodeModVersion = node -e "console.log(process.config.variables.node_module_version)"
|
|
||||||
$keytarTar = "keytar-v${keytarVersion}-node-v${nodeModVersion}-{0}-x64.tar"
|
|
||||||
$keytarTarGz = "${keytarTar}.gz"
|
|
||||||
$keytarUrl = "https://github.com/atom/node-keytar/releases/download/v${keytarVersion}/${keytarTarGz}"
|
|
||||||
|
|
||||||
New-Item -ItemType directory -Path .\keytar\macos | Out-Null
|
|
||||||
New-Item -ItemType directory -Path .\keytar\linux | Out-Null
|
|
||||||
New-Item -ItemType directory -Path .\keytar\windows | Out-Null
|
|
||||||
|
|
||||||
Invoke-RestMethod -Uri $($keytarUrl -f "darwin") -OutFile ".\keytar\macos\$($keytarTarGz -f "darwin")"
|
|
||||||
Invoke-RestMethod -Uri $($keytarUrl -f "linux") -OutFile ".\keytar\linux\$($keytarTarGz -f "linux")"
|
|
||||||
Invoke-RestMethod -Uri $($keytarUrl -f "win32") -OutFile ".\keytar\windows\$($keytarTarGz -f "win32")"
|
|
||||||
|
|
||||||
7z e ".\keytar\macos\$($keytarTarGz -f "darwin")" -o".\keytar\macos"
|
|
||||||
7z e ".\keytar\linux\$($keytarTarGz -f "linux")" -o".\keytar\linux"
|
|
||||||
7z e ".\keytar\windows\$($keytarTarGz -f "win32")" -o".\keytar\windows"
|
|
||||||
|
|
||||||
7z e ".\keytar\macos\$($keytarTar -f "darwin")" -o".\keytar\macos"
|
|
||||||
7z e ".\keytar\linux\$($keytarTar -f "linux")" -o".\keytar\linux"
|
|
||||||
7z e ".\keytar\windows\$($keytarTar -f "win32")" -o".\keytar\windows"
|
|
||||||
}
|
|
||||||
|
|
||||||
before_build:
|
|
||||||
- node --version
|
|
||||||
- npm --version
|
|
||||||
|
|
||||||
build_script:
|
|
||||||
- cmd: |
|
|
||||||
if defined VER_INFO ResourceHacker -open %WIN_PKG% -save %WIN_PKG% -action delete -mask ICONGROUP,1,
|
|
||||||
if defined VER_INFO ResourceHacker -open version-info.rc -save version-info.res -action compile
|
|
||||||
if defined VER_INFO ResourceHacker -open %WIN_PKG% -save %WIN_PKG% -action addoverwrite -resource version-info.res
|
|
||||||
- sh: npm install
|
|
||||||
- sh: npm run rebuild
|
|
||||||
- sh: npm run dist:lin
|
|
||||||
- cmd: npm install
|
|
||||||
- cmd: npm run rebuild
|
|
||||||
- cmd: npm run dist:win:ci
|
|
||||||
- cmd: npm run reset
|
|
||||||
- cmd: npm run dist:cli
|
|
||||||
- cmd: 7z a ./dist-cli/bwdc-windows-%PACKAGE_VERSION%.zip ./dist-cli/windows/bwdc.exe ./keytar/windows/keytar.node
|
|
||||||
- cmd: 7z a ./dist-cli/bwdc-macos-%PACKAGE_VERSION%.zip ./dist-cli/macos/bwdc ./keytar/macos/keytar.node
|
|
||||||
- cmd: 7z a ./dist-cli/bwdc-linux-%PACKAGE_VERSION%.zip ./dist-cli/linux/bwdc ./keytar/linux/keytar.node
|
|
||||||
- ps: |
|
|
||||||
if($isWindows) {
|
|
||||||
Expand-Archive -Path "./dist-cli/bwdc-windows-${env:PACKAGE_VERSION}.zip" -DestinationPath "./test/windows"
|
|
||||||
$testVersion = Invoke-Expression '& ./test/windows/bwdc.exe -v'
|
|
||||||
if($testVersion -ne $env:PACKAGE_VERSION) {
|
|
||||||
Throw "Version test failed."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
- ps: |
|
|
||||||
if($isWindows) {
|
|
||||||
checksum -f="./dist-cli/bwdc-windows-${env:PACKAGE_VERSION}.zip" `
|
|
||||||
-t sha256 | Out-File ./dist-cli/bwdc-windows-sha256-${env:PACKAGE_VERSION}.txt
|
|
||||||
checksum -f="./dist-cli/bwdc-macos-${env:PACKAGE_VERSION}.zip" `
|
|
||||||
-t sha256 | Out-File ./dist-cli/bwdc-macos-sha256-${env:PACKAGE_VERSION}.txt
|
|
||||||
checksum -f="./dist-cli/bwdc-linux-${env:PACKAGE_VERSION}.zip" `
|
|
||||||
-t sha256 | Out-File ./dist-cli/bwdc-linux-sha256-${env:PACKAGE_VERSION}.txt
|
|
||||||
}
|
|
||||||
- ps: |
|
|
||||||
if($isLinux) {
|
|
||||||
Push-AppveyorArtifact ./dist/Bitwarden-Connector-${env:PACKAGE_VERSION}-x86_64.AppImage
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Push-AppveyorArtifact .\dist\Bitwarden-Connector-Portable-${env:PACKAGE_VERSION}.exe
|
|
||||||
Push-AppveyorArtifact .\dist\Bitwarden-Connector-Installer-${env:PACKAGE_VERSION}.exe
|
|
||||||
Push-AppveyorArtifact .\dist-cli\bwdc-windows-${env:PACKAGE_VERSION}.zip
|
|
||||||
Push-AppveyorArtifact .\dist-cli\bwdc-macos-${env:PACKAGE_VERSION}.zip
|
|
||||||
Push-AppveyorArtifact .\dist-cli\bwdc-linux-${env:PACKAGE_VERSION}.zip
|
|
||||||
Push-AppveyorArtifact .\dist-cli\bwdc-windows-sha256-${env:PACKAGE_VERSION}.txt
|
|
||||||
Push-AppveyorArtifact .\dist-cli\bwdc-macos-sha256-${env:PACKAGE_VERSION}.txt
|
|
||||||
Push-AppveyorArtifact .\dist-cli\bwdc-linux-sha256-${env:PACKAGE_VERSION}.txt
|
|
||||||
}
|
|
||||||
|
|
||||||
on_finish:
|
|
||||||
- ps: |
|
|
||||||
if($isWindows -and $env:DEBUG_RDP -eq "true") {
|
|
||||||
$blockRdp = $true
|
|
||||||
iex ((new-object net.webclient).DownloadString(`
|
|
||||||
'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
|
||||||
}
|
|
||||||
|
|
||||||
for:
|
|
||||||
-
|
|
||||||
matrix:
|
|
||||||
only:
|
|
||||||
- image: Visual Studio 2017
|
|
||||||
cache:
|
|
||||||
- '%LOCALAPPDATA%\electron'
|
|
||||||
- '%LOCALAPPDATA%\electron-builder'
|
|
||||||
- 'C:\Users\appveyor\.pkg-cache\'
|
|
||||||
|
|
||||||
-
|
|
||||||
matrix:
|
|
||||||
only:
|
|
||||||
- image: Ubuntu1804
|
|
||||||
cache:
|
|
||||||
- '/home/appveyor/.cache/electron'
|
|
||||||
- '/home/appveyor/.cache/electron-builder'
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
tag: $(APPVEYOR_REPO_TAG_NAME)
|
|
||||||
release: $(RELEASE_NAME)
|
|
||||||
provider: GitHub
|
|
||||||
auth_token: $(GH_TOKEN)
|
|
||||||
artifact: /.*\.(zip|txt)/,
|
|
||||||
force_update: true
|
|
||||||
on:
|
|
||||||
branch: master
|
|
||||||
APPVEYOR_REPO_TAG: true
|
|
||||||
@@ -20,12 +20,6 @@ function webfonts() {
|
|||||||
.pipe(gulp.dest(paths.cssDir));
|
.pipe(gulp.dest(paths.cssDir));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ref: https://github.com/angular/angular/issues/22524
|
|
||||||
function cleanupAotIssue() {
|
|
||||||
return del(['./node_modules/@types/uglify-js/node_modules/source-map/source-map.d.ts']);
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.clean = clean;
|
exports.clean = clean;
|
||||||
exports.cleanupAotIssue = cleanupAotIssue;
|
|
||||||
exports.webfonts = gulp.series(clean, webfonts);
|
exports.webfonts = gulp.series(clean, webfonts);
|
||||||
exports['prebuild:renderer'] = gulp.parallel(webfonts, cleanupAotIssue);;
|
exports['prebuild:renderer'] = webfonts;;
|
||||||
|
|||||||
2
jslib
2
jslib
Submodule jslib updated: 8375f7381a...c70c8ecc24
34728
package-lock.json
generated
34728
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
183
package.json
183
package.json
@@ -9,7 +9,7 @@
|
|||||||
"vault",
|
"vault",
|
||||||
"password manager"
|
"password manager"
|
||||||
],
|
],
|
||||||
"author": "8bit Solutions LLC <hello@bitwarden.com> (https://bitwarden.com)",
|
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
|
||||||
"homepage": "https://bitwarden.com",
|
"homepage": "https://bitwarden.com",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -21,11 +21,14 @@
|
|||||||
"sub:update": "git submodule update --remote",
|
"sub:update": "git submodule update --remote",
|
||||||
"sub:pull": "git submodule foreach git pull origin master",
|
"sub:pull": "git submodule foreach git pull origin master",
|
||||||
"sub:commit": "npm run sub:pull && git commit -am \"update submodule\"",
|
"sub:commit": "npm run sub:pull && git commit -am \"update submodule\"",
|
||||||
"postinstall": "npm run sub:init",
|
"preinstall": "npm run sub:init",
|
||||||
"rebuild": "./node_modules/.bin/electron-rebuild",
|
"symlink:win": "rm -rf ./jslib && cmd /c mklink /J .\\jslib ..\\jslib",
|
||||||
|
"symlink:mac": "npm run symlink:lin",
|
||||||
|
"symlink:lin": "rm -rf ./jslib && ln -s ../jslib ./jslib",
|
||||||
|
"rebuild": "electron-rebuild",
|
||||||
"reset": "rimraf ./node_modules/keytar/* && npm install",
|
"reset": "rimraf ./node_modules/keytar/* && npm install",
|
||||||
"lint": "tslint src/**/*.ts || true",
|
"lint": "tslint 'src/**/*.ts' || true",
|
||||||
"lint:fix": "tslint src/**/*.ts --fix",
|
"lint:fix": "tslint 'src/**/*.ts' --fix",
|
||||||
"build": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\"",
|
"build": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\"",
|
||||||
"build:main": "webpack --config webpack.main.js",
|
"build:main": "webpack --config webpack.main.js",
|
||||||
"build:renderer": "gulp prebuild:renderer && webpack --config webpack.renderer.js",
|
"build:renderer": "gulp prebuild:renderer && webpack --config webpack.renderer.js",
|
||||||
@@ -38,14 +41,14 @@
|
|||||||
"electron": "npm run build:main && concurrently -k -n Main,Rend -c yellow,cyan \"electron --inspect=5858 ./build --watch\" \"npm run build:renderer:watch\"",
|
"electron": "npm run build:main && concurrently -k -n Main,Rend -c yellow,cyan \"electron --inspect=5858 ./build --watch\" \"npm run build:renderer:watch\"",
|
||||||
"clean:dist": "rimraf ./dist/*",
|
"clean:dist": "rimraf ./dist/*",
|
||||||
"clean:dist:cli": "rimraf ./dist-cli/*",
|
"clean:dist:cli": "rimraf ./dist-cli/*",
|
||||||
"pack:lin": "npm run clean:dist && build --linux --x64 -p never",
|
"pack:lin": "npm run clean:dist && electron-builder --linux --x64 -p never",
|
||||||
"pack:mac": "npm run clean:dist && build --mac -p never",
|
"pack:mac": "npm run clean:dist && electron-builder --mac -p never",
|
||||||
"pack:win": "npm run clean:dist && build --win --x64 --ia32 -p never -c.win.certificateSubjectName=\"8bit Solutions LLC\"",
|
"pack:win": "npm run clean:dist && electron-builder --win --x64 --ia32 -p never -c.win.certificateSubjectName=\"8bit Solutions LLC\"",
|
||||||
"pack:win:ci": "npm run clean:dist && build --win --x64 --ia32 -p never",
|
"pack:win:ci": "npm run clean:dist && electron-builder --win --x64 --ia32 -p never",
|
||||||
"pack:cli": "npm run pack:cli:win | npm run pack:cli:mac | npm run pack:cli:lin",
|
"pack:cli": "npm run pack:cli:win | npm run pack:cli:mac | npm run pack:cli:lin",
|
||||||
"pack:cli:win": "pkg . --targets win-x64 --output ./dist-cli/windows/bwdc.exe",
|
"pack:cli:win": "pkg ./src-cli --targets win-x64 --output ./dist-cli/windows/bwdc.exe",
|
||||||
"pack:cli:mac": "pkg . --targets macos-x64 --output ./dist-cli/macos/bwdc",
|
"pack:cli:mac": "pkg ./src-cli --targets macos-x64 --output ./dist-cli/macos/bwdc",
|
||||||
"pack:cli:lin": "pkg . --targets linux-x64 --output ./dist-cli/linux/bwdc",
|
"pack:cli:lin": "pkg ./src-cli --targets linux-x64 --output ./dist-cli/linux/bwdc",
|
||||||
"dist:lin": "npm run build:dist && npm run pack:lin",
|
"dist:lin": "npm run build:dist && npm run pack:lin",
|
||||||
"dist:mac": "npm run build:dist && npm run pack:mac",
|
"dist:mac": "npm run build:dist && npm run pack:mac",
|
||||||
"dist:win": "npm run build:dist && npm run pack:win",
|
"dist:win": "npm run build:dist && npm run pack:win",
|
||||||
@@ -54,20 +57,25 @@
|
|||||||
"dist:cli:win": "npm run build:cli:prod && npm run clean:dist:cli && npm run pack:cli:win",
|
"dist:cli:win": "npm run build:cli:prod && npm run clean:dist:cli && npm run pack:cli:win",
|
||||||
"dist:cli:mac": "npm run build:cli:prod && npm run clean:dist:cli && npm run pack:cli:mac",
|
"dist:cli:mac": "npm run build:cli:prod && npm run clean:dist:cli && npm run pack:cli:mac",
|
||||||
"dist:cli:lin": "npm run build:cli:prod && npm run clean:dist:cli && npm run pack:cli:lin",
|
"dist:cli:lin": "npm run build:cli:prod && npm run clean:dist:cli && npm run pack:cli:lin",
|
||||||
"publish:lin": "npm run build:dist && npm run clean:dist && build --linux --x64 -p always",
|
"publish:lin": "npm run build:dist && npm run clean:dist && electron-builder --linux --x64 -p always",
|
||||||
"publish:mac": "npm run build:dist && npm run clean:dist && build --mac -p always",
|
"publish:mac": "npm run build:dist && npm run clean:dist && electron-builder --mac -p always",
|
||||||
"publish:win": "npm run build:dist && npm run clean:dist && build --win --x64 --ia32 -p always -c.win.certificateSubjectName=\"8bit Solutions LLC\""
|
"publish:win": "npm run build:dist && npm run clean:dist && electron-builder --win --x64 --ia32 -p always -c.win.certificateSubjectName=\"8bit Solutions LLC\""
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "com.bitwarden.directory-connector",
|
"appId": "com.bitwarden.directory-connector",
|
||||||
"copyright": "Copyright © 2015-2018 8bit Solutions LLC",
|
"copyright": "Copyright © 2015-2020 Bitwarden Inc.",
|
||||||
"directories": {
|
"directories": {
|
||||||
"buildResources": "resources",
|
"buildResources": "resources",
|
||||||
"output": "dist",
|
"output": "dist",
|
||||||
"app": "build"
|
"app": "build"
|
||||||
},
|
},
|
||||||
|
"afterSign": "scripts/notarize.js",
|
||||||
"mac": {
|
"mac": {
|
||||||
"category": "public.app-category.productivity",
|
"category": "public.app-category.productivity",
|
||||||
|
"gatekeeperAssess": false,
|
||||||
|
"hardenedRuntime": true,
|
||||||
|
"entitlements": "resources/entitlements.mac.plist",
|
||||||
|
"entitlementsInherit": "resources/entitlements.mac.plist",
|
||||||
"target": [
|
"target": [
|
||||||
"dmg",
|
"dmg",
|
||||||
"zip"
|
"zip"
|
||||||
@@ -77,7 +85,8 @@
|
|||||||
"target": [
|
"target": [
|
||||||
"portable",
|
"portable",
|
||||||
"nsis"
|
"nsis"
|
||||||
]
|
],
|
||||||
|
"sign": "scripts/sign.js"
|
||||||
},
|
},
|
||||||
"linux": {
|
"linux": {
|
||||||
"category": "Utility",
|
"category": "Utility",
|
||||||
@@ -122,97 +131,69 @@
|
|||||||
"artifactName": "Bitwarden-Connector-${version}-${arch}.${ext}"
|
"artifactName": "Bitwarden-Connector-${version}-${arch}.${ext}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bin": {
|
|
||||||
"bwdc": "./build-cli/bwdc.js"
|
|
||||||
},
|
|
||||||
"pkg": {
|
|
||||||
"assets": "./build-cli/**/*"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular/compiler-cli": "^7.2.11",
|
"@angular/compiler-cli": "^11.2.11",
|
||||||
"@microsoft/microsoft-graph-types": "^1.4.0",
|
"@microsoft/microsoft-graph-types": "^1.4.0",
|
||||||
"@ngtools/webpack": "^7.2.2",
|
"@ngtools/webpack": "^11.2.10",
|
||||||
"@types/commander": "^2.12.2",
|
"@types/ldapjs": "^1.0.10",
|
||||||
"@types/form-data": "^2.2.1",
|
"@types/node": "^14.14.43",
|
||||||
"@types/inquirer": "^0.0.43",
|
"@types/proper-lockfile": "^4.1.1",
|
||||||
"@types/ldapjs": "^1.0.3",
|
"clean-webpack-plugin": "^3.0.0",
|
||||||
"@types/lowdb": "^1.0.5",
|
"concurrently": "^6.0.2",
|
||||||
"@types/lunr": "^2.1.6",
|
"copy-webpack-plugin": "^6.4.0",
|
||||||
"@types/node": "^10.9.4",
|
"cross-env": "^7.0.3",
|
||||||
"@types/node-fetch": "^2.1.2",
|
"css-loader": "^5.2.4",
|
||||||
"@types/node-forge": "^0.7.5",
|
"del": "^6.0.0",
|
||||||
"@types/papaparse": "^4.5.3",
|
"electron-builder": "^22.10.5",
|
||||||
"@types/semver": "^5.5.0",
|
"electron-notarize": "^1.0.0",
|
||||||
"@types/source-map": "0.5.2",
|
"electron-rebuild": "^2.3.5",
|
||||||
"@types/webcrypto": "^0.0.28",
|
"electron-reload": "^1.5.0",
|
||||||
"@types/webpack": "^4.4.11",
|
"file-loader": "^6.2.0",
|
||||||
"@types/zxcvbn": "4.4.0",
|
|
||||||
"clean-webpack-plugin": "^0.1.19",
|
|
||||||
"concurrently": "^4.0.1",
|
|
||||||
"copy-webpack-plugin": "^4.5.2",
|
|
||||||
"cross-env": "^5.2.0",
|
|
||||||
"css-loader": "^1.0.0",
|
|
||||||
"del": "^3.0.0",
|
|
||||||
"electron": "3.0.14",
|
|
||||||
"electron-builder": "20.38.5",
|
|
||||||
"electron-rebuild": "^1.8.2",
|
|
||||||
"electron-reload": "^1.4.0",
|
|
||||||
"extract-text-webpack-plugin": "next",
|
|
||||||
"file-loader": "^2.0.0",
|
|
||||||
"font-awesome": "4.7.0",
|
"font-awesome": "4.7.0",
|
||||||
"gulp": "^4.0.0",
|
"gulp": "^4.0.2",
|
||||||
"gulp-google-webfonts": "^2.0.0",
|
"gulp-google-webfonts": "^4.0.0",
|
||||||
"html-loader": "^0.5.5",
|
"html-loader": "^1.3.2",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^4.5.1",
|
||||||
"node-abi": "^2.5.1",
|
"mini-css-extract-plugin": "^1.5.0",
|
||||||
"node-loader": "^0.6.0",
|
"node-loader": "^1.0.3",
|
||||||
"node-sass": "^4.11.0",
|
"pkg": "^5.1.0",
|
||||||
"pkg": "4.3.4",
|
"rimraf": "^3.0.2",
|
||||||
"rimraf": "^2.6.2",
|
"sass": "^1.32.11",
|
||||||
"sass-loader": "^7.1.0",
|
"sass-loader": "^10.1.1",
|
||||||
"ts-loader": "^5.3.3",
|
"tapable": "^1.1.3",
|
||||||
"tslint": "^5.12.1",
|
"ts-loader": "^8.1.0",
|
||||||
|
"tsconfig-paths-webpack-plugin": "^3.5.1",
|
||||||
|
"tslint": "~6.1.0",
|
||||||
"tslint-loader": "^3.5.4",
|
"tslint-loader": "^3.5.4",
|
||||||
"typescript": "3.2.4",
|
"typescript": "4.1.5",
|
||||||
"webpack": "^4.29.0",
|
"webpack": "^4.46.0",
|
||||||
"webpack-cli": "^3.2.1",
|
"webpack-cli": "^4.6.0",
|
||||||
"webpack-merge": "^4.2.1",
|
"webpack-merge": "^5.7.3",
|
||||||
"webpack-node-externals": "^1.7.2"
|
"webpack-node-externals": "^3.0.0",
|
||||||
|
"prebuild-install": "^5.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "7.2.1",
|
"@bitwarden/jslib-angular": "file:jslib/angular",
|
||||||
"@angular/common": "7.2.1",
|
"@bitwarden/jslib-common": "file:jslib/common",
|
||||||
"@angular/compiler": "7.2.1",
|
"@bitwarden/jslib-electron": "file:jslib/electron",
|
||||||
"@angular/core": "7.2.1",
|
"@bitwarden/jslib-node": "file:jslib/node",
|
||||||
"@angular/forms": "7.2.1",
|
"@microsoft/microsoft-graph-client": "^2.2.1",
|
||||||
"@angular/http": "7.2.1",
|
"angular2-toaster": "^11.0.1",
|
||||||
"@angular/platform-browser": "7.2.1",
|
"bootstrap": "^4.6.0",
|
||||||
"@angular/platform-browser-dynamic": "7.2.1",
|
"chalk": "^4.1.1",
|
||||||
"@angular/router": "7.2.1",
|
"commander": "^7.2.0",
|
||||||
"@angular/upgrade": "7.2.1",
|
"core-js": "^3.11.0",
|
||||||
"@microsoft/microsoft-graph-client": "1.2.0",
|
|
||||||
"@okta/okta-sdk-nodejs": "1.2.0",
|
|
||||||
"angular2-toaster": "6.1.0",
|
|
||||||
"angulartics2": "6.3.0",
|
|
||||||
"big-integer": "1.6.36",
|
|
||||||
"bootstrap": "4.3.1",
|
|
||||||
"chalk": "2.4.1",
|
|
||||||
"commander": "2.18.0",
|
|
||||||
"core-js": "2.6.2",
|
|
||||||
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git",
|
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git",
|
||||||
"electron-log": "2.2.17",
|
"form-data": "^4.0.0",
|
||||||
"electron-store": "1.3.0",
|
"googleapis": "^73.0.0",
|
||||||
"electron-updater": "4.0.6",
|
"inquirer": "8.0.0",
|
||||||
"form-data": "2.3.2",
|
|
||||||
"googleapis": "33.0.0",
|
|
||||||
"inquirer": "6.2.0",
|
|
||||||
"keytar": "4.4.1",
|
|
||||||
"ldapjs": "git+https://git@github.com/kspearrin/node-ldapjs.git",
|
"ldapjs": "git+https://git@github.com/kspearrin/node-ldapjs.git",
|
||||||
"lowdb": "1.0.0",
|
"lunr": "^2.3.9",
|
||||||
"lunr": "2.3.3",
|
"open": "^8.0.6",
|
||||||
"node-fetch": "2.2.0",
|
"proper-lockfile": "^4.1.2"
|
||||||
"node-forge": "0.7.6",
|
},
|
||||||
"rxjs": "6.3.3",
|
"engines": {
|
||||||
"zone.js": "0.8.28"
|
"node": "~14",
|
||||||
|
"npm": "~7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
resources/entitlements.mac.plist
Normal file
10
resources/entitlements.mac.plist
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.disable-library-validation</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -12,14 +12,14 @@ BLOCK "StringFileInfo"
|
|||||||
{
|
{
|
||||||
BLOCK "040904b0"
|
BLOCK "040904b0"
|
||||||
{
|
{
|
||||||
VALUE "CompanyName", "8bit Solutions LLC"
|
VALUE "CompanyName", "Bitwarden Inc."
|
||||||
VALUE "ProductName", "Bitwarden"
|
VALUE "ProductName", "Bitwarden"
|
||||||
VALUE "FileDescription", "Bitwarden Directory Connector CLI"
|
VALUE "FileDescription", "Bitwarden Directory Connector CLI"
|
||||||
VALUE "FileVersion", "$env:PACKAGE_VERSION"
|
VALUE "FileVersion", "$env:PACKAGE_VERSION"
|
||||||
VALUE "ProductVersion", "$env:PACKAGE_VERSION"
|
VALUE "ProductVersion", "$env:PACKAGE_VERSION"
|
||||||
VALUE "OriginalFilename", "bwdc.exe"
|
VALUE "OriginalFilename", "bwdc.exe"
|
||||||
VALUE "InternalName", "bwdc"
|
VALUE "InternalName", "bwdc"
|
||||||
VALUE "LegalCopyright", "Copyright 8bit Solutions LLC"
|
VALUE "LegalCopyright", "Copyright Bitwarden Inc."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
18
scripts/notarize.js
Normal file
18
scripts/notarize.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
require('dotenv').config();
|
||||||
|
const { notarize } = require('electron-notarize');
|
||||||
|
|
||||||
|
exports.default = async function notarizing(context) {
|
||||||
|
const { electronPlatformName, appOutDir } = context;
|
||||||
|
if (electronPlatformName !== 'darwin') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const appleId = process.env.APPLE_ID_USERNAME || process.env.APPLEID;
|
||||||
|
const appleIdPassword = process.env.APPLE_ID_PASSWORD || `@keychain:AC_PASSWORD`;
|
||||||
|
const appName = context.packager.appInfo.productFilename;
|
||||||
|
return await notarize({
|
||||||
|
appBundleId: 'com.bitwarden.directory-connector',
|
||||||
|
appPath: `${appOutDir}/${appName}.app`,
|
||||||
|
appleId: appleId,
|
||||||
|
appleIdPassword: appleIdPassword,
|
||||||
|
});
|
||||||
|
};
|
||||||
23
scripts/sign.js
Normal file
23
scripts/sign.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
exports.default = async function(configuration) {
|
||||||
|
if (
|
||||||
|
parseInt(process.env.ELECTRON_BUILDER_SIGN) === 1 &&
|
||||||
|
configuration.path.slice(-4) == ".exe"
|
||||||
|
) {
|
||||||
|
console.log(`[*] Signing file: ${configuration.path}`)
|
||||||
|
require("child_process").execSync(
|
||||||
|
`azuresigntool sign ` +
|
||||||
|
`-kvu ${process.env.SIGNING_VAULT_URL} ` +
|
||||||
|
`-kvi ${process.env.SIGNING_CLIENT_ID} ` +
|
||||||
|
`-kvt ${process.env.SIGNING_TENANT_ID} ` +
|
||||||
|
`-kvs ${process.env.SIGNING_CLIENT_SECRET} ` +
|
||||||
|
`-kvc ${process.env.SIGNING_CERT_NAME} ` +
|
||||||
|
`-fd ${configuration.hash} ` +
|
||||||
|
`-du ${configuration.site} ` +
|
||||||
|
`-tr http://timestamp.digicert.com ` +
|
||||||
|
`"${configuration.path}"`,
|
||||||
|
{
|
||||||
|
stdio: "inherit"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
24
src-cli/package.json
Normal file
24
src-cli/package.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "bitwarden-directory-connector",
|
||||||
|
"productName": "Bitwarden Directory Connector",
|
||||||
|
"description": "Sync your user directory to your Bitwarden organization.",
|
||||||
|
"version": "2.9.4",
|
||||||
|
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
|
||||||
|
"homepage": "https://bitwarden.com",
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"main": "main.js",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/bitwarden/directory-connector"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"bwdc": "../build-cli/bwdc.js"
|
||||||
|
},
|
||||||
|
"pkg": {
|
||||||
|
"assets": "../build-cli/**/*"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"browser-hrtime": "^1.1.8",
|
||||||
|
"keytar": "7.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/app/accounts/apiKey.component.html
Normal file
47
src/app/accounts/apiKey.component.html
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<div class="container-fluid">
|
||||||
|
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-8 col-lg-6">
|
||||||
|
<p class="text-center font-weight-bold">{{'welcome' | i18n}}</p>
|
||||||
|
<p class="text-center">{{'logInDesc' | i18n}}</p>
|
||||||
|
<div class="card">
|
||||||
|
<h5 class="card-header">{{'logIn' | i18n}}</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="client_id">{{'clientId' | i18n}}</label>
|
||||||
|
<input id="client_id" name="ClientId" [(ngModel)]="clientId"
|
||||||
|
class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="row-main">
|
||||||
|
<label for="client_secret">{{'clientSecret' | i18n}}</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="{{showSecret ? 'text' : 'password'}}" id="client_secret" name="ClientSecret"
|
||||||
|
[(ngModel)]="clientSecret" class="form-control">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button type="button" class="ml-1 btn btn-link" appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleSecret()">
|
||||||
|
<i class="fa fa-lg" aria-hidden="true"[ngClass]="showSecret ? 'fa-eye-slash' : 'fa-eye'"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex">
|
||||||
|
<div>
|
||||||
|
<button type="submit" class="btn btn-primary" [disabled]="form.loading">
|
||||||
|
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!form.loading"></i>
|
||||||
|
<i class="fa fa-sign-in fa-fw" [hidden]="form.loading"></i>
|
||||||
|
{{'logIn' | i18n}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-link ml-auto" (click)="settings()">
|
||||||
|
{{'settings' | i18n}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<ng-template #environment></ng-template>
|
||||||
85
src/app/accounts/apiKey.component.ts
Normal file
85
src/app/accounts/apiKey.component.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import {
|
||||||
|
Component,
|
||||||
|
ComponentFactoryResolver,
|
||||||
|
Input,
|
||||||
|
ViewChild,
|
||||||
|
ViewContainerRef,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { EnvironmentComponent } from './environment.component';
|
||||||
|
|
||||||
|
import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service';
|
||||||
|
import { AuthService } from 'jslib-common/abstractions/auth.service';
|
||||||
|
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||||
|
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||||
|
|
||||||
|
import { ModalComponent } from 'jslib-angular/components/modal.component';
|
||||||
|
import { Utils } from 'jslib-common/misc/utils';
|
||||||
|
import { ConfigurationService } from '../../services/configuration.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-apiKey',
|
||||||
|
templateUrl: 'apiKey.component.html',
|
||||||
|
})
|
||||||
|
export class ApiKeyComponent {
|
||||||
|
@ViewChild('environment', { read: ViewContainerRef, static: true }) environmentModal: ViewContainerRef;
|
||||||
|
@Input() clientId: string = '';
|
||||||
|
@Input() clientSecret: string = '';
|
||||||
|
|
||||||
|
formPromise: Promise<any>;
|
||||||
|
successRoute = '/tabs/dashboard';
|
||||||
|
showSecret: boolean = false;
|
||||||
|
|
||||||
|
constructor(private authService: AuthService, private apiKeyService: ApiKeyService, private router: Router,
|
||||||
|
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
|
||||||
|
private configurationService: ConfigurationService, private platformUtilsService: PlatformUtilsService) { }
|
||||||
|
|
||||||
|
async submit() {
|
||||||
|
if (this.clientId == null || this.clientId === '') {
|
||||||
|
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||||
|
this.i18nService.t('clientIdRequired'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.clientId.startsWith('organization')) {
|
||||||
|
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||||
|
this.i18nService.t('orgApiKeyRequired'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.clientSecret == null || this.clientSecret === '') {
|
||||||
|
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||||
|
this.i18nService.t('clientSecretRequired'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const idParts = this.clientId.split('.');
|
||||||
|
|
||||||
|
if (idParts.length !== 2 || idParts[0] !== 'organization' || !Utils.isGuid(idParts[1])) {
|
||||||
|
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||||
|
this.i18nService.t('invalidClientId'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.formPromise = this.authService.logInApiKey(this.clientId, this.clientSecret);
|
||||||
|
await this.formPromise;
|
||||||
|
const organizationId = await this.apiKeyService.getEntityId();
|
||||||
|
await this.configurationService.saveOrganizationId(organizationId);
|
||||||
|
this.router.navigate([this.successRoute]);
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
settings() {
|
||||||
|
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
||||||
|
const modal = this.environmentModal.createComponent(factory).instance;
|
||||||
|
const childComponent = modal.show<EnvironmentComponent>(EnvironmentComponent,
|
||||||
|
this.environmentModal);
|
||||||
|
|
||||||
|
childComponent.onSaved.subscribe(() => {
|
||||||
|
modal.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
toggleSecret() {
|
||||||
|
this.showSecret = !this.showSecret;
|
||||||
|
document.getElementById('client_secret').focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<h4>{{'customEnvironment' | i18n}}</h4>
|
<h4>{{'customEnvironment' | i18n}}</h4>
|
||||||
<p>{{'customEnvironmentFooter' | i18n}}</p>
|
<p>{{'customEnvironmentFooter' | i18n}}</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="webVaultUrl">{{'webVaultUrl' | i18n}}</label>
|
||||||
|
<input id="webVaultUrl" type="text" name="WebVaultUrl" [(ngModel)]="webVaultUrl"
|
||||||
|
class="form-control">
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="apiUrl">{{'apiUrl' | i18n}}</label>
|
<label for="apiUrl">{{'apiUrl' | i18n}}</label>
|
||||||
<input id="apiUrl" type="text" name="ApiUrl" [(ngModel)]="apiUrl" class="form-control">
|
<input id="apiUrl" type="text" name="ApiUrl" [(ngModel)]="apiUrl" class="form-control">
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||||
|
|
||||||
import { EnvironmentComponent as BaseEnvironmentComponent } from 'jslib/angular/components/environment.component';
|
import { EnvironmentComponent as BaseEnvironmentComponent } from 'jslib-angular/components/environment.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-environment',
|
selector: 'app-environment',
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
<div class="container-fluid">
|
|
||||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-md-8 col-lg-6">
|
|
||||||
<p class="text-center font-weight-bold">{{'welcome' | i18n}}</p>
|
|
||||||
<p class="text-center">{{'logInDesc' | i18n}}</p>
|
|
||||||
<div class="card">
|
|
||||||
<h5 class="card-header">{{'logIn' | i18n}}</h5>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="email">{{'emailAddress' | i18n}}</label>
|
|
||||||
<input id="email" type="text" name="Email" [(ngModel)]="email" class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="row-main">
|
|
||||||
<label for="masterPassword">{{'masterPass' | i18n}}</label>
|
|
||||||
<input id="masterPassword" type="password" name="MasterPassword"
|
|
||||||
[(ngModel)]="masterPassword" class="form-control">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary" [disabled]="form.loading">
|
|
||||||
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!form.loading"></i>
|
|
||||||
<i class="fa fa-sign-in fa-fw" [hidden]="form.loading"></i>
|
|
||||||
{{'logIn' | i18n}}
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-link" (click)="settings()">
|
|
||||||
{{'settings' | i18n}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<ng-template #environment></ng-template>
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import {
|
|
||||||
Component,
|
|
||||||
ComponentFactoryResolver,
|
|
||||||
ViewChild,
|
|
||||||
ViewContainerRef,
|
|
||||||
} from '@angular/core';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
|
|
||||||
import { EnvironmentComponent } from './environment.component';
|
|
||||||
|
|
||||||
import { AuthService } from 'jslib/abstractions/auth.service';
|
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
|
||||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
|
||||||
import { StorageService } from 'jslib/abstractions/storage.service';
|
|
||||||
|
|
||||||
import { LoginComponent as BaseLoginComponent } from 'jslib/angular/components/login.component';
|
|
||||||
import { ModalComponent } from 'jslib/angular/components/modal.component';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-login',
|
|
||||||
templateUrl: 'login.component.html',
|
|
||||||
})
|
|
||||||
export class LoginComponent extends BaseLoginComponent {
|
|
||||||
@ViewChild('environment', { read: ViewContainerRef }) environmentModal: ViewContainerRef;
|
|
||||||
|
|
||||||
constructor(authService: AuthService, router: Router,
|
|
||||||
i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
|
|
||||||
storageService: StorageService, platformUtilsService: PlatformUtilsService) {
|
|
||||||
super(authService, router, platformUtilsService, i18nService, storageService);
|
|
||||||
super.successRoute = '/tabs/dashboard';
|
|
||||||
}
|
|
||||||
|
|
||||||
settings() {
|
|
||||||
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
|
||||||
const modal = this.environmentModal.createComponent(factory).instance;
|
|
||||||
const childComponent = modal.show<EnvironmentComponent>(EnvironmentComponent,
|
|
||||||
this.environmentModal);
|
|
||||||
|
|
||||||
childComponent.onSaved.subscribe(() => {
|
|
||||||
modal.close();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<div class="modal fade">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h3 class="modal-title">{{'twoStepOptions' | i18n}}</h3>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" title="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p *ngFor="let p of providers">
|
|
||||||
<a href="#" appStopClick (click)="choose(p)">
|
|
||||||
<strong>{{p.name}}</strong>
|
|
||||||
</a>
|
|
||||||
<br /> {{p.description}}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a href="#" (click)="recover()">
|
|
||||||
<strong>{{'recoveryCodeTitle' | i18n}}</strong>
|
|
||||||
</a>
|
|
||||||
<br /> {{'recoveryCodeDesc' | i18n}}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
|
|
||||||
import { AuthService } from 'jslib/abstractions/auth.service';
|
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
|
||||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
|
||||||
|
|
||||||
import {
|
|
||||||
TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent,
|
|
||||||
} from 'jslib/angular/components/two-factor-options.component';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-two-factor-options',
|
|
||||||
templateUrl: 'two-factor-options.component.html',
|
|
||||||
})
|
|
||||||
export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
|
|
||||||
constructor(authService: AuthService, router: Router,
|
|
||||||
i18nService: I18nService, platformUtilsService: PlatformUtilsService) {
|
|
||||||
super(authService, router, i18nService, platformUtilsService, window);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
<div class="container-fluid">
|
|
||||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-md-8 col-lg-6">
|
|
||||||
<div class="card">
|
|
||||||
<h5 class="card-header">{{title}}</h5>
|
|
||||||
<div class="card-body">
|
|
||||||
<ng-container
|
|
||||||
*ngIf="selectedProviderType === providerType.Email || selectedProviderType === providerType.Authenticator">
|
|
||||||
<p *ngIf="selectedProviderType === providerType.Authenticator">
|
|
||||||
{{'enterVerificationCodeApp' | i18n}}
|
|
||||||
</p>
|
|
||||||
<p *ngIf="selectedProviderType === providerType.Email">
|
|
||||||
{{'enterVerificationCodeEmail' | i18n : twoFactorEmail}}
|
|
||||||
</p>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="code">{{'verificationCode' | i18n}}</label>
|
|
||||||
<input id="code" type="text" name="Code" [(ngModel)]="token" appAutofocus
|
|
||||||
class="form-control">
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="selectedProviderType === providerType.Yubikey">
|
|
||||||
<p>{{'insertYubiKey' | i18n}}</p>
|
|
||||||
<p><img src="../../images/yubikey.jpg" class="img-fluid rounded" alt=""></p>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="code">{{'verificationCode' | i18n}}</label>
|
|
||||||
<input id="code" type="password" name="Code" [(ngModel)]="token" appAutofocus
|
|
||||||
class="form-control">
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="selectedProviderType === providerType.Duo ||
|
|
||||||
selectedProviderType === providerType.OrganizationDuo">
|
|
||||||
<div id="duo-frame">
|
|
||||||
<iframe id="duo_iframe"></iframe>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
<div class="form-group"
|
|
||||||
*ngIf="selectedProviderType != null && selectedProviderType !== providerType.U2f">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="remember" [(ngModel)]="remember"
|
|
||||||
name="Remember">
|
|
||||||
<label class="form-check-label" for="remember">{{'rememberMe' | i18n}}</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ng-container class="card-body"
|
|
||||||
*ngIf="selectedProviderType === null || selectedProviderType === providerType.U2f">
|
|
||||||
<p>{{'noTwoStepProviders' | i18n}}</p>
|
|
||||||
<p>{{'noTwoStepProviders2' | i18n}}</p>
|
|
||||||
</ng-container>
|
|
||||||
<button type="submit" class="btn btn-primary" [disabled]="form.loading" *ngIf="selectedProviderType != null && selectedProviderType !== providerType.U2f && selectedProviderType !== providerType.Duo &&
|
|
||||||
selectedProviderType !== providerType.OrganizationDuo">
|
|
||||||
<i class="fa fa-sign-in fa-fw" [hidden]="form.loading"></i>
|
|
||||||
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!form.loading"></i>
|
|
||||||
{{'continue' | i18n}}
|
|
||||||
</button>
|
|
||||||
<a routerLink="/login" class="btn btn-link">{{'cancel' | i18n}}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-center mt-3">
|
|
||||||
<a href="#" appStopClick (click)="anotherMethod()">{{'useAnotherTwoStepMethod' | i18n}}</a>
|
|
||||||
<a href="#" appStopClick (click)="sendEmail(true)" [appApiAction]="emailPromise"
|
|
||||||
*ngIf="selectedProviderType === providerType.Email">
|
|
||||||
{{'sendVerificationCodeEmailAgain' | i18n}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<ng-template #twoFactorOptions></ng-template>
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import {
|
|
||||||
Component,
|
|
||||||
ComponentFactoryResolver,
|
|
||||||
ViewChild,
|
|
||||||
ViewContainerRef,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
|
|
||||||
import { TwoFactorOptionsComponent } from './two-factor-options.component';
|
|
||||||
|
|
||||||
import { TwoFactorProviderType } from 'jslib/enums/twoFactorProviderType';
|
|
||||||
|
|
||||||
import { ApiService } from 'jslib/abstractions/api.service';
|
|
||||||
import { AuthService } from 'jslib/abstractions/auth.service';
|
|
||||||
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
|
||||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
|
||||||
|
|
||||||
import { ModalComponent } from 'jslib/angular/components/modal.component';
|
|
||||||
import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib/angular/components/two-factor.component';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-two-factor',
|
|
||||||
templateUrl: 'two-factor.component.html',
|
|
||||||
})
|
|
||||||
export class TwoFactorComponent extends BaseTwoFactorComponent {
|
|
||||||
@ViewChild('twoFactorOptions', { read: ViewContainerRef }) twoFactorOptionsModal: ViewContainerRef;
|
|
||||||
|
|
||||||
constructor(authService: AuthService, router: Router,
|
|
||||||
i18nService: I18nService, apiService: ApiService,
|
|
||||||
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
|
|
||||||
private componentFactoryResolver: ComponentFactoryResolver) {
|
|
||||||
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService);
|
|
||||||
super.successRoute = '/tabs/dashboard';
|
|
||||||
}
|
|
||||||
|
|
||||||
anotherMethod() {
|
|
||||||
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
|
||||||
const modal = this.twoFactorOptionsModal.createComponent(factory).instance;
|
|
||||||
const childComponent = modal.show<TwoFactorOptionsComponent>(TwoFactorOptionsComponent,
|
|
||||||
this.twoFactorOptionsModal);
|
|
||||||
|
|
||||||
childComponent.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => {
|
|
||||||
modal.close();
|
|
||||||
this.selectedProviderType = provider;
|
|
||||||
await this.init();
|
|
||||||
});
|
|
||||||
childComponent.onRecoverSelected.subscribe(() => {
|
|
||||||
modal.close();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,8 +7,7 @@ import {
|
|||||||
import { AuthGuardService } from './services/auth-guard.service';
|
import { AuthGuardService } from './services/auth-guard.service';
|
||||||
import { LaunchGuardService } from './services/launch-guard.service';
|
import { LaunchGuardService } from './services/launch-guard.service';
|
||||||
|
|
||||||
import { LoginComponent } from './accounts/login.component';
|
import { ApiKeyComponent } from './accounts/apiKey.component';
|
||||||
import { TwoFactorComponent } from './accounts/two-factor.component';
|
|
||||||
import { DashboardComponent } from './tabs/dashboard.component';
|
import { DashboardComponent } from './tabs/dashboard.component';
|
||||||
import { MoreComponent } from './tabs/more.component';
|
import { MoreComponent } from './tabs/more.component';
|
||||||
import { SettingsComponent } from './tabs/settings.component';
|
import { SettingsComponent } from './tabs/settings.component';
|
||||||
@@ -18,10 +17,9 @@ const routes: Routes = [
|
|||||||
{ path: '', redirectTo: '/login', pathMatch: 'full' },
|
{ path: '', redirectTo: '/login', pathMatch: 'full' },
|
||||||
{
|
{
|
||||||
path: 'login',
|
path: 'login',
|
||||||
component: LoginComponent,
|
component: ApiKeyComponent,
|
||||||
canActivate: [LaunchGuardService],
|
canActivate: [LaunchGuardService],
|
||||||
},
|
},
|
||||||
{ path: '2fa', component: TwoFactorComponent },
|
|
||||||
{
|
{
|
||||||
path: 'tabs',
|
path: 'tabs',
|
||||||
component: TabsComponent,
|
component: TabsComponent,
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import {
|
|||||||
ToasterContainerComponent,
|
ToasterContainerComponent,
|
||||||
ToasterService,
|
ToasterService,
|
||||||
} from 'angular2-toaster';
|
} from 'angular2-toaster';
|
||||||
import { Angulartics2 } from 'angulartics2';
|
|
||||||
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
@@ -21,18 +19,18 @@ import {
|
|||||||
import { DomSanitizer } from '@angular/platform-browser';
|
import { DomSanitizer } from '@angular/platform-browser';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
import { ModalComponent } from 'jslib/angular/components/modal.component';
|
import { ModalComponent } from 'jslib-angular/components/modal.component';
|
||||||
|
|
||||||
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
|
import { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
|
||||||
|
|
||||||
import { ApiService } from 'jslib/abstractions/api.service';
|
import { ApiService } from 'jslib-common/abstractions/api.service';
|
||||||
import { AuthService } from 'jslib/abstractions/auth.service';
|
import { AuthService } from 'jslib-common/abstractions/auth.service';
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||||
import { StateService } from 'jslib/abstractions/state.service';
|
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||||
import { StorageService } from 'jslib/abstractions/storage.service';
|
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
||||||
import { TokenService } from 'jslib/abstractions/token.service';
|
import { TokenService } from 'jslib-common/abstractions/token.service';
|
||||||
import { UserService } from 'jslib/abstractions/user.service';
|
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||||
|
|
||||||
import { ConfigurationService } from '../services/configuration.service';
|
import { ConfigurationService } from '../services/configuration.service';
|
||||||
import { SyncService } from '../services/sync.service';
|
import { SyncService } from '../services/sync.service';
|
||||||
@@ -48,7 +46,7 @@ const BroadcasterSubscriptionId = 'AppComponent';
|
|||||||
<router-outlet></router-outlet>`,
|
<router-outlet></router-outlet>`,
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit {
|
export class AppComponent implements OnInit {
|
||||||
@ViewChild('settings', { read: ViewContainerRef }) settingsRef: ViewContainerRef;
|
@ViewChild('settings', { read: ViewContainerRef, static: true }) settingsRef: ViewContainerRef;
|
||||||
|
|
||||||
toasterConfig: ToasterConfig = new ToasterConfig({
|
toasterConfig: ToasterConfig = new ToasterConfig({
|
||||||
showCloseButton: true,
|
showCloseButton: true,
|
||||||
@@ -60,10 +58,9 @@ export class AppComponent implements OnInit {
|
|||||||
private lastActivity: number = null;
|
private lastActivity: number = null;
|
||||||
private modal: ModalComponent = null;
|
private modal: ModalComponent = null;
|
||||||
|
|
||||||
constructor(private angulartics2GoogleAnalytics: Angulartics2GoogleAnalytics,
|
constructor(private broadcasterService: BroadcasterService, private userService: UserService,
|
||||||
private broadcasterService: BroadcasterService, private userService: UserService,
|
|
||||||
private tokenService: TokenService, private storageService: StorageService,
|
private tokenService: TokenService, private storageService: StorageService,
|
||||||
private authService: AuthService, private router: Router, private analytics: Angulartics2,
|
private authService: AuthService, private router: Router,
|
||||||
private toasterService: ToasterService, private i18nService: I18nService,
|
private toasterService: ToasterService, private i18nService: I18nService,
|
||||||
private sanitizer: DomSanitizer, private ngZone: NgZone,
|
private sanitizer: DomSanitizer, private ngZone: NgZone,
|
||||||
private componentFactoryResolver: ComponentFactoryResolver, private messagingService: MessagingService,
|
private componentFactoryResolver: ComponentFactoryResolver, private messagingService: MessagingService,
|
||||||
@@ -76,12 +73,6 @@ export class AppComponent implements OnInit {
|
|||||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
||||||
this.ngZone.run(async () => {
|
this.ngZone.run(async () => {
|
||||||
switch (message.command) {
|
switch (message.command) {
|
||||||
case 'loggedIn':
|
|
||||||
if (await this.userService.isAuthenticated()) {
|
|
||||||
const profile = await this.apiService.getProfile();
|
|
||||||
this.stateService.save('profileOrganizations', profile.organizations);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'syncScheduleStarted':
|
case 'syncScheduleStarted':
|
||||||
case 'syncScheduleStopped':
|
case 'syncScheduleStopped':
|
||||||
this.stateService.save('syncingDir', message.command === 'syncScheduleStarted');
|
this.stateService.save('syncingDir', message.command === 'syncScheduleStarted');
|
||||||
@@ -127,11 +118,8 @@ export class AppComponent implements OnInit {
|
|||||||
case 'showToast':
|
case 'showToast':
|
||||||
this.showToast(message);
|
this.showToast(message);
|
||||||
break;
|
break;
|
||||||
case 'analyticsEventTrack':
|
case 'ssoCallback':
|
||||||
this.analytics.eventTrack.next({
|
this.router.navigate(['sso'], { queryParams: { code: message.code, state: message.state } });
|
||||||
action: message.action,
|
|
||||||
properties: { label: message.label },
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
@@ -146,13 +134,10 @@ export class AppComponent implements OnInit {
|
|||||||
private async logOut(expired: boolean) {
|
private async logOut(expired: boolean) {
|
||||||
const userId = await this.userService.getUserId();
|
const userId = await this.userService.getUserId();
|
||||||
|
|
||||||
await Promise.all([
|
await this.tokenService.clearToken();
|
||||||
this.tokenService.clearToken(),
|
await this.userService.clear();
|
||||||
this.userService.clear(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
this.authService.logOut(async () => {
|
this.authService.logOut(async () => {
|
||||||
this.analytics.eventTrack.next({ action: 'Logged Out' });
|
|
||||||
if (expired) {
|
if (expired) {
|
||||||
this.toasterService.popAsync('warning', this.i18nService.t('loggedOut'),
|
this.toasterService.popAsync('warning', this.i18nService.t('loggedOut'),
|
||||||
this.i18nService.t('loginExpired'));
|
this.i18nService.t('loginExpired'));
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import 'core-js';
|
import 'core-js/stable';
|
||||||
import 'zone.js/dist/zone';
|
import 'zone.js/dist/zone';
|
||||||
|
|
||||||
import { ToasterModule } from 'angular2-toaster';
|
import { ToasterModule } from 'angular2-toaster';
|
||||||
import { Angulartics2Module } from 'angulartics2';
|
|
||||||
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
|
||||||
|
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
import { ServicesModule } from './services/services.module';
|
import { ServicesModule } from './services/services.module';
|
||||||
@@ -15,29 +13,28 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
|||||||
|
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
|
|
||||||
import { IconComponent } from 'jslib/angular/components/icon.component';
|
import { CalloutComponent } from 'jslib-angular/components/callout.component';
|
||||||
import { ModalComponent } from 'jslib/angular/components/modal.component';
|
import { IconComponent } from 'jslib-angular/components/icon.component';
|
||||||
|
import { ModalComponent } from 'jslib-angular/components/modal.component';
|
||||||
|
|
||||||
|
import { ApiKeyComponent } from './accounts/apiKey.component';
|
||||||
import { EnvironmentComponent } from './accounts/environment.component';
|
import { EnvironmentComponent } from './accounts/environment.component';
|
||||||
import { LoginComponent } from './accounts/login.component';
|
|
||||||
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component';
|
|
||||||
import { TwoFactorComponent } from './accounts/two-factor.component';
|
|
||||||
import { DashboardComponent } from './tabs/dashboard.component';
|
import { DashboardComponent } from './tabs/dashboard.component';
|
||||||
import { MoreComponent } from './tabs/more.component';
|
import { MoreComponent } from './tabs/more.component';
|
||||||
import { SettingsComponent } from './tabs/settings.component';
|
import { SettingsComponent } from './tabs/settings.component';
|
||||||
import { TabsComponent } from './tabs/tabs.component';
|
import { TabsComponent } from './tabs/tabs.component';
|
||||||
|
|
||||||
import { A11yTitleDirective } from 'jslib/angular/directives/a11y-title.directive';
|
import { A11yTitleDirective } from 'jslib-angular/directives/a11y-title.directive';
|
||||||
import { ApiActionDirective } from 'jslib/angular/directives/api-action.directive';
|
import { ApiActionDirective } from 'jslib-angular/directives/api-action.directive';
|
||||||
import { AutofocusDirective } from 'jslib/angular/directives/autofocus.directive';
|
import { AutofocusDirective } from 'jslib-angular/directives/autofocus.directive';
|
||||||
import { BlurClickDirective } from 'jslib/angular/directives/blur-click.directive';
|
import { BlurClickDirective } from 'jslib-angular/directives/blur-click.directive';
|
||||||
import { BoxRowDirective } from 'jslib/angular/directives/box-row.directive';
|
import { BoxRowDirective } from 'jslib-angular/directives/box-row.directive';
|
||||||
import { FallbackSrcDirective } from 'jslib/angular/directives/fallback-src.directive';
|
import { FallbackSrcDirective } from 'jslib-angular/directives/fallback-src.directive';
|
||||||
import { StopClickDirective } from 'jslib/angular/directives/stop-click.directive';
|
import { StopClickDirective } from 'jslib-angular/directives/stop-click.directive';
|
||||||
import { StopPropDirective } from 'jslib/angular/directives/stop-prop.directive';
|
import { StopPropDirective } from 'jslib-angular/directives/stop-prop.directive';
|
||||||
|
|
||||||
import { I18nPipe } from 'jslib/angular/pipes/i18n.pipe';
|
import { I18nPipe } from 'jslib-angular/pipes/i18n.pipe';
|
||||||
import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
|
import { SearchCiphersPipe } from 'jslib-angular/pipes/search-ciphers.pipe';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -46,26 +43,22 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
|
|||||||
FormsModule,
|
FormsModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
ServicesModule,
|
ServicesModule,
|
||||||
Angulartics2Module.forRoot([Angulartics2GoogleAnalytics], {
|
|
||||||
pageTracking: {
|
|
||||||
clearQueryParams: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
ToasterModule.forRoot(),
|
ToasterModule.forRoot(),
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
A11yTitleDirective,
|
A11yTitleDirective,
|
||||||
ApiActionDirective,
|
ApiActionDirective,
|
||||||
|
ApiKeyComponent,
|
||||||
AppComponent,
|
AppComponent,
|
||||||
AutofocusDirective,
|
AutofocusDirective,
|
||||||
BlurClickDirective,
|
BlurClickDirective,
|
||||||
BoxRowDirective,
|
BoxRowDirective,
|
||||||
|
CalloutComponent,
|
||||||
DashboardComponent,
|
DashboardComponent,
|
||||||
EnvironmentComponent,
|
EnvironmentComponent,
|
||||||
FallbackSrcDirective,
|
FallbackSrcDirective,
|
||||||
I18nPipe,
|
I18nPipe,
|
||||||
IconComponent,
|
IconComponent,
|
||||||
LoginComponent,
|
|
||||||
ModalComponent,
|
ModalComponent,
|
||||||
MoreComponent,
|
MoreComponent,
|
||||||
SearchCiphersPipe,
|
SearchCiphersPipe,
|
||||||
@@ -73,13 +66,10 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
|
|||||||
StopClickDirective,
|
StopClickDirective,
|
||||||
StopPropDirective,
|
StopPropDirective,
|
||||||
TabsComponent,
|
TabsComponent,
|
||||||
TwoFactorComponent,
|
|
||||||
TwoFactorOptionsComponent,
|
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
EnvironmentComponent,
|
EnvironmentComponent,
|
||||||
ModalComponent,
|
ModalComponent,
|
||||||
TwoFactorOptionsComponent,
|
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core';
|
|
||||||
|
|
||||||
import { InputVerbatimDirective } from 'jslib/angular/directives/input-verbatim.directive';
|
|
||||||
import { TrueFalseValueDirective } from 'jslib/angular/directives/true-false-value.directive';
|
|
||||||
import { SearchPipe } from 'jslib/angular/pipes/search.pipe';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [],
|
|
||||||
declarations: [
|
|
||||||
InputVerbatimDirective,
|
|
||||||
TrueFalseValueDirective,
|
|
||||||
SearchPipe,
|
|
||||||
],
|
|
||||||
})
|
|
||||||
export class DummyModule {
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { enableProdMode } from '@angular/core';
|
import { enableProdMode } from '@angular/core';
|
||||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||||
|
|
||||||
import { isDev } from 'jslib/electron/utils';
|
import { isDev } from 'jslib-electron/utils';
|
||||||
|
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
require('../scss/styles.scss');
|
require('../scss/styles.scss');
|
||||||
|
|||||||
@@ -3,17 +3,17 @@ import {
|
|||||||
CanActivate,
|
CanActivate,
|
||||||
Router,
|
Router,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
|
import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service';
|
||||||
|
|
||||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||||
import { UserService } from 'jslib/abstractions/user.service';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthGuardService implements CanActivate {
|
export class AuthGuardService implements CanActivate {
|
||||||
constructor(private userService: UserService, private router: Router,
|
constructor(private apiKeyService: ApiKeyService, private router: Router,
|
||||||
private messagingService: MessagingService) { }
|
private messagingService: MessagingService) { }
|
||||||
|
|
||||||
async canActivate() {
|
async canActivate() {
|
||||||
const isAuthed = await this.userService.isAuthenticated();
|
const isAuthed = await this.apiKeyService.isAuthenticated();
|
||||||
if (!isAuthed) {
|
if (!isAuthed) {
|
||||||
this.messagingService.send('logout');
|
this.messagingService.send('logout');
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ import {
|
|||||||
Router,
|
Router,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
|
|
||||||
import { UserService } from 'jslib/abstractions/user.service';
|
import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LaunchGuardService implements CanActivate {
|
export class LaunchGuardService implements CanActivate {
|
||||||
constructor(private userService: UserService, private router: Router) { }
|
constructor(private apiKeyService: ApiKeyService, private router: Router) { }
|
||||||
|
|
||||||
async canActivate() {
|
async canActivate() {
|
||||||
const isAuthed = await this.userService.isAuthenticated();
|
const isAuthed = await this.apiKeyService.isAuthenticated();
|
||||||
if (!isAuthed) {
|
if (!isAuthed) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { remote } from 'electron';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
APP_INITIALIZER,
|
APP_INITIALIZER,
|
||||||
NgModule,
|
NgModule,
|
||||||
@@ -7,11 +5,11 @@ import {
|
|||||||
|
|
||||||
import { ToasterModule } from 'angular2-toaster';
|
import { ToasterModule } from 'angular2-toaster';
|
||||||
|
|
||||||
import { ElectronLogService } from 'jslib/electron/services/electronLog.service';
|
import { ElectronLogService } from 'jslib-electron/services/electronLog.service';
|
||||||
import { ElectronPlatformUtilsService } from 'jslib/electron/services/electronPlatformUtils.service';
|
import { ElectronPlatformUtilsService } from 'jslib-electron/services/electronPlatformUtils.service';
|
||||||
import { ElectronRendererMessagingService } from 'jslib/electron/services/electronRendererMessaging.service';
|
import { ElectronRendererMessagingService } from 'jslib-electron/services/electronRendererMessaging.service';
|
||||||
import { ElectronRendererSecureStorageService } from 'jslib/electron/services/electronRendererSecureStorage.service';
|
import { ElectronRendererSecureStorageService } from 'jslib-electron/services/electronRendererSecureStorage.service';
|
||||||
import { ElectronStorageService } from 'jslib/electron/services/electronStorage.service';
|
import { ElectronRendererStorageService } from 'jslib-electron/services/electronRendererStorage.service';
|
||||||
|
|
||||||
import { AuthGuardService } from './auth-guard.service';
|
import { AuthGuardService } from './auth-guard.service';
|
||||||
import { LaunchGuardService } from './launch-guard.service';
|
import { LaunchGuardService } from './launch-guard.service';
|
||||||
@@ -20,64 +18,78 @@ import { ConfigurationService } from '../../services/configuration.service';
|
|||||||
import { I18nService } from '../../services/i18n.service';
|
import { I18nService } from '../../services/i18n.service';
|
||||||
import { SyncService } from '../../services/sync.service';
|
import { SyncService } from '../../services/sync.service';
|
||||||
|
|
||||||
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
|
import { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
|
||||||
import { ValidationService } from 'jslib/angular/services/validation.service';
|
import { ValidationService } from 'jslib-angular/services/validation.service';
|
||||||
|
|
||||||
import { Analytics } from 'jslib/misc/analytics';
|
import { ApiKeyService } from 'jslib-common/services/apiKey.service';
|
||||||
|
import { AppIdService } from 'jslib-common/services/appId.service';
|
||||||
|
import { ConstantsService } from 'jslib-common/services/constants.service';
|
||||||
|
import { ContainerService } from 'jslib-common/services/container.service';
|
||||||
|
import { CryptoService } from 'jslib-common/services/crypto.service';
|
||||||
|
import { EnvironmentService } from 'jslib-common/services/environment.service';
|
||||||
|
import { PasswordGenerationService } from 'jslib-common/services/passwordGeneration.service';
|
||||||
|
import { PolicyService } from 'jslib-common/services/policy.service';
|
||||||
|
import { StateService } from 'jslib-common/services/state.service';
|
||||||
|
import { TokenService } from 'jslib-common/services/token.service';
|
||||||
|
import { UserService } from 'jslib-common/services/user.service';
|
||||||
|
|
||||||
import { ApiService } from 'jslib/services/api.service';
|
import { NodeCryptoFunctionService } from 'jslib-node/services/nodeCryptoFunction.service';
|
||||||
import { AppIdService } from 'jslib/services/appId.service';
|
|
||||||
import { AuthService } from 'jslib/services/auth.service';
|
|
||||||
import { ConstantsService } from 'jslib/services/constants.service';
|
|
||||||
import { ContainerService } from 'jslib/services/container.service';
|
|
||||||
import { CryptoService } from 'jslib/services/crypto.service';
|
|
||||||
import { EnvironmentService } from 'jslib/services/environment.service';
|
|
||||||
import { NodeCryptoFunctionService } from 'jslib/services/nodeCryptoFunction.service';
|
|
||||||
import { StateService } from 'jslib/services/state.service';
|
|
||||||
import { TokenService } from 'jslib/services/token.service';
|
|
||||||
import { UserService } from 'jslib/services/user.service';
|
|
||||||
|
|
||||||
import { ApiService as ApiServiceAbstraction } from 'jslib/abstractions/api.service';
|
import { ApiService as ApiServiceAbstraction } from 'jslib-common/abstractions/api.service';
|
||||||
import { AppIdService as AppIdServiceAbstraction } from 'jslib/abstractions/appId.service';
|
import { ApiKeyService as ApiKeyServiceAbstraction } from 'jslib-common/abstractions/apiKey.service';
|
||||||
import { AuthService as AuthServiceAbstraction } from 'jslib/abstractions/auth.service';
|
import { AuthService as AuthServiceAbstraction } from 'jslib-common/abstractions/auth.service';
|
||||||
import { CryptoService as CryptoServiceAbstraction } from 'jslib/abstractions/crypto.service';
|
import { CryptoService as CryptoServiceAbstraction } from 'jslib-common/abstractions/crypto.service';
|
||||||
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib/abstractions/cryptoFunction.service';
|
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib-common/abstractions/cryptoFunction.service';
|
||||||
import { EnvironmentService as EnvironmentServiceAbstraction } from 'jslib/abstractions/environment.service';
|
import { EnvironmentService as EnvironmentServiceAbstraction } from 'jslib-common/abstractions/environment.service';
|
||||||
import { I18nService as I18nServiceAbstraction } from 'jslib/abstractions/i18n.service';
|
import { I18nService as I18nServiceAbstraction } from 'jslib-common/abstractions/i18n.service';
|
||||||
import { LogService as LogServiceAbstraction } from 'jslib/abstractions/log.service';
|
import { LogService as LogServiceAbstraction } from 'jslib-common/abstractions/log.service';
|
||||||
import { MessagingService as MessagingServiceAbstraction } from 'jslib/abstractions/messaging.service';
|
import { MessagingService as MessagingServiceAbstraction } from 'jslib-common/abstractions/messaging.service';
|
||||||
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib/abstractions/platformUtils.service';
|
import {
|
||||||
import { StateService as StateServiceAbstraction } from 'jslib/abstractions/state.service';
|
PasswordGenerationService as PasswordGenerationServiceAbstraction,
|
||||||
import { StorageService as StorageServiceAbstraction } from 'jslib/abstractions/storage.service';
|
} from 'jslib-common/abstractions/passwordGeneration.service';
|
||||||
import { TokenService as TokenServiceAbstraction } from 'jslib/abstractions/token.service';
|
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib-common/abstractions/platformUtils.service';
|
||||||
import { UserService as UserServiceAbstraction } from 'jslib/abstractions/user.service';
|
import { PolicyService as PolicyServiceAbstraction } from 'jslib-common/abstractions/policy.service';
|
||||||
|
import { StateService as StateServiceAbstraction } from 'jslib-common/abstractions/state.service';
|
||||||
|
import { StorageService as StorageServiceAbstraction } from 'jslib-common/abstractions/storage.service';
|
||||||
|
import { TokenService as TokenServiceAbstraction } from 'jslib-common/abstractions/token.service';
|
||||||
|
import { UserService as UserServiceAbstraction } from 'jslib-common/abstractions/user.service';
|
||||||
|
|
||||||
|
import { ApiService, refreshToken } from '../../services/api.service';
|
||||||
|
import { AuthService } from '../../services/auth.service';
|
||||||
|
|
||||||
const logService = new ElectronLogService();
|
const logService = new ElectronLogService();
|
||||||
const i18nService = new I18nService(window.navigator.language, './locales');
|
const i18nService = new I18nService(window.navigator.language, './locales');
|
||||||
const stateService = new StateService();
|
const stateService = new StateService();
|
||||||
const broadcasterService = new BroadcasterService();
|
const broadcasterService = new BroadcasterService();
|
||||||
const messagingService = new ElectronRendererMessagingService(broadcasterService);
|
const messagingService = new ElectronRendererMessagingService(broadcasterService);
|
||||||
const platformUtilsService = new ElectronPlatformUtilsService(i18nService, messagingService, true);
|
const storageService: StorageServiceAbstraction = new ElectronRendererStorageService();
|
||||||
const storageService: StorageServiceAbstraction = new ElectronStorageService(remote.app.getPath('userData'));
|
const platformUtilsService = new ElectronPlatformUtilsService(i18nService, messagingService, false, storageService);
|
||||||
const secureStorageService: StorageServiceAbstraction = new ElectronRendererSecureStorageService();
|
const secureStorageService: StorageServiceAbstraction = new ElectronRendererSecureStorageService();
|
||||||
const cryptoFunctionService: CryptoFunctionServiceAbstraction = new NodeCryptoFunctionService();
|
const cryptoFunctionService: CryptoFunctionServiceAbstraction = new NodeCryptoFunctionService();
|
||||||
const cryptoService = new CryptoService(storageService, secureStorageService, cryptoFunctionService);
|
const cryptoService = new CryptoService(storageService, secureStorageService, cryptoFunctionService,
|
||||||
|
platformUtilsService, logService);
|
||||||
const appIdService = new AppIdService(storageService);
|
const appIdService = new AppIdService(storageService);
|
||||||
const tokenService = new TokenService(storageService);
|
const tokenService = new TokenService(storageService);
|
||||||
const apiService = new ApiService(tokenService, platformUtilsService,
|
const environmentService = new EnvironmentService(storageService);
|
||||||
|
const apiService = new ApiService(tokenService, platformUtilsService, environmentService, refreshTokenCallback,
|
||||||
async (expired: boolean) => messagingService.send('logout', { expired: expired }));
|
async (expired: boolean) => messagingService.send('logout', { expired: expired }));
|
||||||
const environmentService = new EnvironmentService(apiService, storageService, null);
|
|
||||||
const userService = new UserService(tokenService, storageService);
|
const userService = new UserService(tokenService, storageService);
|
||||||
|
const apiKeyService = new ApiKeyService(tokenService, storageService);
|
||||||
const containerService = new ContainerService(cryptoService);
|
const containerService = new ContainerService(cryptoService);
|
||||||
const authService = new AuthService(cryptoService, apiService, userService, tokenService, appIdService,
|
const authService = new AuthService(cryptoService, apiService, userService, tokenService, appIdService,
|
||||||
i18nService, platformUtilsService, messagingService, false);
|
i18nService, platformUtilsService, messagingService, null, logService, apiKeyService, false);
|
||||||
const configurationService = new ConfigurationService(storageService, secureStorageService);
|
const configurationService = new ConfigurationService(storageService, secureStorageService);
|
||||||
const syncService = new SyncService(configurationService, logService, cryptoFunctionService, apiService,
|
const syncService = new SyncService(configurationService, logService, cryptoFunctionService, apiService,
|
||||||
messagingService, i18nService);
|
messagingService, i18nService, environmentService);
|
||||||
|
const passwordGenerationService = new PasswordGenerationService(cryptoService, storageService, null);
|
||||||
|
const policyService = new PolicyService(userService, storageService);
|
||||||
|
|
||||||
const analytics = new Analytics(window, () => true, platformUtilsService, storageService, appIdService);
|
|
||||||
containerService.attachToWindow(window);
|
containerService.attachToWindow(window);
|
||||||
|
|
||||||
|
function refreshTokenCallback(): Promise<any> {
|
||||||
|
return refreshToken(apiKeyService, authService);
|
||||||
|
}
|
||||||
|
|
||||||
export function initFactory(): Function {
|
export function initFactory(): Function {
|
||||||
return async () => {
|
return async () => {
|
||||||
await environmentService.setUrlsFromStorage();
|
await environmentService.setUrlsFromStorage();
|
||||||
@@ -90,7 +102,7 @@ export function initFactory(): Function {
|
|||||||
|
|
||||||
let installAction = null;
|
let installAction = null;
|
||||||
const installedVersion = await storageService.get<string>(ConstantsService.installedVersionKey);
|
const installedVersion = await storageService.get<string>(ConstantsService.installedVersionKey);
|
||||||
const currentVersion = platformUtilsService.getApplicationVersion();
|
const currentVersion = await platformUtilsService.getApplicationVersion();
|
||||||
if (installedVersion == null) {
|
if (installedVersion == null) {
|
||||||
installAction = 'install';
|
installAction = 'install';
|
||||||
} else if (installedVersion !== currentVersion) {
|
} else if (installedVersion !== currentVersion) {
|
||||||
@@ -127,6 +139,7 @@ export function initFactory(): Function {
|
|||||||
{ provide: PlatformUtilsServiceAbstraction, useValue: platformUtilsService },
|
{ provide: PlatformUtilsServiceAbstraction, useValue: platformUtilsService },
|
||||||
{ provide: ApiServiceAbstraction, useValue: apiService },
|
{ provide: ApiServiceAbstraction, useValue: apiService },
|
||||||
{ provide: UserServiceAbstraction, useValue: userService },
|
{ provide: UserServiceAbstraction, useValue: userService },
|
||||||
|
{ provide: ApiKeyServiceAbstraction, useValue: apiKeyService },
|
||||||
{ provide: MessagingServiceAbstraction, useValue: messagingService },
|
{ provide: MessagingServiceAbstraction, useValue: messagingService },
|
||||||
{ provide: BroadcasterService, useValue: broadcasterService },
|
{ provide: BroadcasterService, useValue: broadcasterService },
|
||||||
{ provide: StorageServiceAbstraction, useValue: storageService },
|
{ provide: StorageServiceAbstraction, useValue: storageService },
|
||||||
@@ -134,6 +147,9 @@ export function initFactory(): Function {
|
|||||||
{ provide: LogServiceAbstraction, useValue: logService },
|
{ provide: LogServiceAbstraction, useValue: logService },
|
||||||
{ provide: ConfigurationService, useValue: configurationService },
|
{ provide: ConfigurationService, useValue: configurationService },
|
||||||
{ provide: SyncService, useValue: syncService },
|
{ provide: SyncService, useValue: syncService },
|
||||||
|
{ provide: PasswordGenerationServiceAbstraction, useValue: passwordGenerationService },
|
||||||
|
{ provide: CryptoFunctionServiceAbstraction, useValue: cryptoFunctionService },
|
||||||
|
{ provide: PolicyServiceAbstraction, useValue: policyService },
|
||||||
{
|
{
|
||||||
provide: APP_INITIALIZER,
|
provide: APP_INITIALIZER,
|
||||||
useFactory: initFactory,
|
useFactory: initFactory,
|
||||||
|
|||||||
@@ -14,38 +14,44 @@
|
|||||||
<strong *ngIf="syncRunning" class="text-success">{{'running' | i18n}}</strong>
|
<strong *ngIf="syncRunning" class="text-success">{{'running' | i18n}}</strong>
|
||||||
<strong *ngIf="!syncRunning" class="text-danger">{{'stopped' | i18n}}</strong>
|
<strong *ngIf="!syncRunning" class="text-danger">{{'stopped' | i18n}}</strong>
|
||||||
</p>
|
</p>
|
||||||
<button #startBtn (click)="start()" [appApiAction]="startPromise" class="btn btn-primary"
|
<form #startForm [appApiAction]="startPromise" class="d-inline">
|
||||||
[disabled]="startBtn.loading">
|
<button (click)="start()" class="btn btn-primary"
|
||||||
<i class="fa fa-play fa-fw" [hidden]="startBtn.loading"></i>
|
[disabled]="startForm.loading">
|
||||||
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!startBtn.loading"></i>
|
<i class="fa fa-play fa-fw" [hidden]="startForm.loading"></i>
|
||||||
{{'startSync' | i18n}}
|
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!startForm.loading"></i>
|
||||||
</button>
|
{{'startSync' | i18n}}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
<button (click)="stop()" class="btn btn-primary">
|
<button (click)="stop()" class="btn btn-primary">
|
||||||
<i class="fa fa-stop fa-fw"></i>
|
<i class="fa fa-stop fa-fw"></i>
|
||||||
{{'stopSync' | i18n}}
|
{{'stopSync' | i18n}}
|
||||||
</button>
|
</button>
|
||||||
<button #syncBtn (click)="sync()" [appApiAction]="syncPromise" class="btn btn-primary"
|
<form #syncForm [appApiAction]="syncPromise" class="d-inline">
|
||||||
[disabled]="syncBtn.loading">
|
<button (click)="sync()" class="btn btn-primary"
|
||||||
<i class="fa fa-refresh fa-fw" [ngClass]="{'fa-spin': syncBtn.loading}"></i>
|
[disabled]="syncForm.loading">
|
||||||
{{'syncNow' | i18n}}
|
<i class="fa fa-refresh fa-fw" [ngClass]="{'fa-spin': syncForm.loading}"></i>
|
||||||
</button>
|
{{'syncNow' | i18n}}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3 class="card-header">{{'testing' | i18n}}</h3>
|
<h3 class="card-header">{{'testing' | i18n}}</h3>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>{{'testingDesc' | i18n}}</p>
|
<p>{{'testingDesc' | i18n}}</p>
|
||||||
<button #simBtn (click)="simulate()" [appApiAction]="simPromise" class="btn btn-primary"
|
<form #simForm [appApiAction]="simPromise" class="d-inline">
|
||||||
[disabled]="simBtn.loading">
|
<button (click)="simulate()" class="btn btn-primary"
|
||||||
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!simBtn.loading"></i>
|
[disabled]="simForm.loading">
|
||||||
<i class="fa fa-bug fa-fw" [hidden]="simBtn.loading"></i>
|
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!simForm.loading"></i>
|
||||||
{{'testNow' | i18n}}
|
<i class="fa fa-bug fa-fw" [hidden]="simForm.loading"></i>
|
||||||
</button>
|
{{'testNow' | i18n}}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
<div class="form-check mt-2">
|
<div class="form-check mt-2">
|
||||||
<input class="form-check-input" type="checkbox" id="simSinceLast" [(ngModel)]="simSinceLast">
|
<input class="form-check-input" type="checkbox" id="simSinceLast" [(ngModel)]="simSinceLast">
|
||||||
<label class="form-check-label" for="simSinceLast">{{'testLastSync' | i18n}}</label>
|
<label class="form-check-label" for="simSinceLast">{{'testLastSync' | i18n}}</label>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="!simBtn.loading && (simUsers || simGroups)">
|
<ng-container *ngIf="!simForm.loading && (simUsers || simGroups)">
|
||||||
<hr />
|
<hr />
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg">
|
<div class="col-lg">
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import {
|
|||||||
|
|
||||||
import { ToasterService } from 'angular2-toaster';
|
import { ToasterService } from 'angular2-toaster';
|
||||||
|
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||||
import { StateService } from 'jslib/abstractions/state.service';
|
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||||
|
|
||||||
import { SyncService } from '../../services/sync.service';
|
import { SyncService } from '../../services/sync.service';
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ import { SimResult } from '../../models/simResult';
|
|||||||
import { UserEntry } from '../../models/userEntry';
|
import { UserEntry } from '../../models/userEntry';
|
||||||
import { ConfigurationService } from '../../services/configuration.service';
|
import { ConfigurationService } from '../../services/configuration.service';
|
||||||
|
|
||||||
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
|
import { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
|
||||||
|
|
||||||
import { ConnectorUtils } from '../../utils';
|
import { ConnectorUtils } from '../../utils';
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<p>
|
<p>
|
||||||
{{'bitwardenDirectoryConnector' | i18n}}
|
{{'bitwardenDirectoryConnector' | i18n}}
|
||||||
<br /> {{'version' | i18n : version}}
|
<br /> {{'version' | i18n : version}}
|
||||||
<br /> © 8bit Solutions LLC 2015-{{year}}
|
<br /> © Bitwarden Inc. LLC 2015-{{year}}
|
||||||
</p>
|
</p>
|
||||||
<button class="btn btn-primary" type="button" (click)="update()" [disabled]="checkingForUpdate">
|
<button class="btn btn-primary" type="button" (click)="update()" [disabled]="checkingForUpdate">
|
||||||
<i class="fa fa-download fa-fw" [hidden]="checkingForUpdate"></i>
|
<i class="fa fa-download fa-fw" [hidden]="checkingForUpdate"></i>
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import {
|
|||||||
|
|
||||||
import { ToasterService } from 'angular2-toaster';
|
import { ToasterService } from 'angular2-toaster';
|
||||||
|
|
||||||
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
|
import { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
|
||||||
|
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||||
|
|
||||||
import { ConfigurationService } from '../../services/configuration.service';
|
import { ConfigurationService } from '../../services/configuration.service';
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ export class MoreComponent implements OnInit {
|
|||||||
private toasterService: ToasterService, private broadcasterService: BroadcasterService,
|
private toasterService: ToasterService, private broadcasterService: BroadcasterService,
|
||||||
private ngZone: NgZone, private changeDetectorRef: ChangeDetectorRef) { }
|
private ngZone: NgZone, private changeDetectorRef: ChangeDetectorRef) { }
|
||||||
|
|
||||||
ngOnInit() {
|
async ngOnInit() {
|
||||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
||||||
this.ngZone.run(async () => {
|
this.ngZone.run(async () => {
|
||||||
switch (message.command) {
|
switch (message.command) {
|
||||||
@@ -51,7 +51,7 @@ export class MoreComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.year = new Date().getFullYear().toString();
|
this.year = new Date().getFullYear().toString();
|
||||||
this.version = this.platformUtilsService.getApplicationVersion();
|
this.version = await this.platformUtilsService.getApplicationVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
|
|||||||
@@ -33,41 +33,73 @@
|
|||||||
<label class="form-check-label" for="ad">{{'ldapAd' | i18n}}</label>
|
<label class="form-check-label" for="ad">{{'ldapAd' | i18n}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group" *ngIf="!ldap.ad">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" id="pagedSearch"
|
||||||
|
[(ngModel)]="ldap.pagedSearch" name="PagedSearch">
|
||||||
|
<label class="form-check-label" for="pagedSearch">{{'ldapPagedResults' | i18n}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="ssl" [(ngModel)]="ldap.ssl" name="SSL">
|
<input class="form-check-input" type="checkbox" id="ldapEncrypted" [(ngModel)]="ldap.ssl"
|
||||||
<label class="form-check-label" for="ssl">{{'ldapSsl' | i18n}}</label>
|
name="Encrypted">
|
||||||
|
<label class="form-check-label" for="ldapEncrypted">{{'ldapEncrypted' | i18n}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4" *ngIf="ldap.ssl">
|
<div class="ml-4" *ngIf="ldap.ssl">
|
||||||
<p>{{'ldapSslUntrustedDesc' | i18n}}</p>
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="sslCertPath">{{'ldapSslCert' | i18n}}</label>
|
<div class="form-radio">
|
||||||
<input type="file" class="form-control-file mb-2" id="sslCertPath_file"
|
<input class="form-radio-input" type="radio" [value]="false" id="ssl"
|
||||||
(change)="setSslPath('sslCertPath')">
|
[(ngModel)]="ldap.startTls" name="SSL">
|
||||||
<input type="text" class="form-control" id="sslCertPath" name="SSLCertPath"
|
<label class="form-radio-label" for="ssl">{{'ldapSsl' | i18n}}</label>
|
||||||
[(ngModel)]="ldap.sslCertPath">
|
</div>
|
||||||
|
<div class="form-radio">
|
||||||
|
<input class="form-radio-input" type="radio" [value]="true" id="startTls"
|
||||||
|
[(ngModel)]="ldap.startTls" name="StartTLS">
|
||||||
|
<label class="form-radio-label" for="startTls">{{'ldapTls' | i18n}}</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="ml-4" *ngIf="ldap.startTls">
|
||||||
<label for="sslKeyPath">{{'ldapSslKey' | i18n}}</label>
|
<p>{{'ldapTlsUntrustedDesc' | i18n}}</p>
|
||||||
<input type="file" class="form-control-file mb-2" id="sslKeyPath_file"
|
<div class="form-group">
|
||||||
(change)="setSslPath('sslKeyPath')">
|
<label for="tlsCaPath">{{'ldapTlsCa' | i18n}}</label>
|
||||||
<input type="text" class="form-control" id="sslKeyPath" name="SSLKeyPath"
|
<input type="file" class="form-control-file mb-2" id="tlsCaPath_file"
|
||||||
[(ngModel)]="ldap.sslKeyPath">
|
(change)="setSslPath('tlsCaPath')">
|
||||||
|
<input type="text" class="form-control" id="tlsCaPath" name="TLSCaPath"
|
||||||
|
[(ngModel)]="ldap.tlsCaPath">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="ml-4" *ngIf="!ldap.startTls">
|
||||||
<label for="sslCaPath">{{'ldapSslCa' | i18n}}</label>
|
<p>{{'ldapSslUntrustedDesc' | i18n}}</p>
|
||||||
<input type="file" class="form-control-file mb-2" id="sslCaPath_file"
|
<div class="form-group">
|
||||||
(change)="setSslPath('sslCaPath')">
|
<label for="sslCertPath">{{'ldapSslCert' | i18n}}</label>
|
||||||
<input type="text" class="form-control" id="sslCaPath" name="SSLCaPath"
|
<input type="file" class="form-control-file mb-2" id="sslCertPath_file"
|
||||||
[(ngModel)]="ldap.sslCaPath">
|
(change)="setSslPath('sslCertPath')">
|
||||||
|
<input type="text" class="form-control" id="sslCertPath" name="SSLCertPath"
|
||||||
|
[(ngModel)]="ldap.sslCertPath">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sslKeyPath">{{'ldapSslKey' | i18n}}</label>
|
||||||
|
<input type="file" class="form-control-file mb-2" id="sslKeyPath_file"
|
||||||
|
(change)="setSslPath('sslKeyPath')">
|
||||||
|
<input type="text" class="form-control" id="sslKeyPath" name="SSLKeyPath"
|
||||||
|
[(ngModel)]="ldap.sslKeyPath">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sslCaPath">{{'ldapSslCa' | i18n}}</label>
|
||||||
|
<input type="file" class="form-control-file mb-2" id="sslCaPath_file"
|
||||||
|
(change)="setSslPath('sslCaPath')">
|
||||||
|
<input type="text" class="form-control" id="sslCaPath" name="SSLCaPath"
|
||||||
|
[(ngModel)]="ldap.sslCaPath">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="sslAllowUnauthorized"
|
<input class="form-check-input" type="checkbox" id="certDoNotVerify"
|
||||||
[(ngModel)]="ldap.sslAllowUnauthorized" name="SSLAllowUnauthorized">
|
[(ngModel)]="ldap.sslAllowUnauthorized" name="CertDoNoVerify">
|
||||||
<label class="form-check-label"
|
<label class="form-check-label" for="certDoNotVerify">{{'ldapCertDoNotVerify' |
|
||||||
for="sslAllowUnauthorized">{{'ldapSslAllowUnauthorized' | i18n}}</label>
|
i18n}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -89,8 +121,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="password">{{'password' | i18n}}</label>
|
<label for="password">{{'password' | i18n}}</label>
|
||||||
<input type="password" class="form-control" id="password" name="Password"
|
<div class="input-group">
|
||||||
|
<input type="{{showLdapPassword ? 'text' : 'password'}}" class="form-control" id="password" name="Password"
|
||||||
[(ngModel)]="ldap.password">
|
[(ngModel)]="ldap.password">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleLdapPassword()">
|
||||||
|
<i class="fa fa-lg" aria-hidden="true"[ngClass]="showLdapPassword ? 'fa-eye-slash' : 'fa-eye'"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -107,7 +146,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="secretKey">{{'secretKey' | i18n}}</label>
|
<label for="secretKey">{{'secretKey' | i18n}}</label>
|
||||||
<input type="text" class="form-control" id="secretKey" name="SecretKey" [(ngModel)]="azure.key">
|
<div class="input-group">
|
||||||
|
<input type="{{showAzureKey ? 'text' : 'password'}}" class="form-control" id="secretKey" name="SecretKey"
|
||||||
|
[(ngModel)]="azure.key">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleAzureKey()">
|
||||||
|
<i class="fa fa-lg" aria-hidden="true"[ngClass]="showAzureKey ? 'fa-eye-slash' : 'fa-eye'"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div [hidden]="directory != directoryType.Okta">
|
<div [hidden]="directory != directoryType.Okta">
|
||||||
@@ -118,8 +165,42 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="oktaToken">{{'token' | i18n}}</label>
|
<label for="oktaToken">{{'token' | i18n}}</label>
|
||||||
<input type="text" class="form-control" id="oktaToken" name="OktaToken"
|
<div class="input-group">
|
||||||
|
<input type="{{showOktaKey ? 'text' : 'password'}}" class="form-control" id="oktaToken" name="OktaToken"
|
||||||
[(ngModel)]="okta.token">
|
[(ngModel)]="okta.token">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleOktaKey()">
|
||||||
|
<i class="fa fa-lg" aria-hidden="true"[ngClass]="showOktaKey ? 'fa-eye-slash' : 'fa-eye'"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div [hidden]="directory != directoryType.OneLogin">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="oneLoginClientId">{{'clientId' | i18n}}</label>
|
||||||
|
<input type="text" class="form-control" id="oneLoginClientId" name="OneLoginClientId"
|
||||||
|
[(ngModel)]="oneLogin.clientId">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="oneLoginClientSecret">{{'clientSecret' | i18n}}</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="{{showOneLoginSecret ? 'text' : 'password'}}" class="form-control" id="oneLoginClientSecret" name="OneLoginClientSecret"
|
||||||
|
[(ngModel)]="oneLogin.clientSecret">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleOneLoginSecret()">
|
||||||
|
<i class="fa fa-lg" aria-hidden="true"[ngClass]="showOneLoginSecret ? 'fa-eye-slash' : 'fa-eye'"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="oneLoginRegion">{{'region' | i18n}}</label>
|
||||||
|
<select class="form-control" id="oneLoginRegion" name="OneLoginRegion"
|
||||||
|
[(ngModel)]="oneLogin.region">
|
||||||
|
<option value="us">US</option>
|
||||||
|
<option value="eu">EU</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div [hidden]="directory != directoryType.GSuite">
|
<div [hidden]="directory != directoryType.GSuite">
|
||||||
@@ -161,17 +242,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card mb-3">
|
|
||||||
<h3 class="card-header">{{'account' | i18n}}</h3>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="organizationId">{{'organization' | i18n}}</label>
|
|
||||||
<select class="form-control" id="organizationId" name="OrganizationId" [(ngModel)]="organizationId">
|
|
||||||
<option *ngFor="let o of organizationOptions" [ngValue]="o.value">{{o.name}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
@@ -197,6 +267,13 @@
|
|||||||
<label class="form-check-label" for="overwriteExisting">{{'overwriteExisting' | i18n}}</label>
|
<label class="form-check-label" for="overwriteExisting">{{'overwriteExisting' | i18n}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" id="largeImport" [(ngModel)]="sync.largeImport"
|
||||||
|
name="LargeImport">
|
||||||
|
<label class="form-check-label" for="largeImport">{{'largeImport' | i18n}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div [hidden]="directory != directoryType.Ldap">
|
<div [hidden]="directory != directoryType.Ldap">
|
||||||
<div [hidden]="ldap.ad">
|
<div [hidden]="ldap.ad">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -218,7 +295,8 @@
|
|||||||
<small class="text-muted form-text">{{'ex' | i18n}} whenChanged</small>
|
<small class="text-muted form-text">{{'ex' | i18n}} whenChanged</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div [hidden]="directory != directoryType.Ldap && directory != directoryType.OneLogin">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="useEmailPrefixSuffix"
|
<input class="form-check-input" type="checkbox" id="useEmailPrefixSuffix"
|
||||||
@@ -228,7 +306,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div [hidden]="!sync.useEmailPrefixSuffix">
|
<div [hidden]="!sync.useEmailPrefixSuffix">
|
||||||
<div class="form-group" [hidden]="ldap.ad">
|
<div class="form-group" [hidden]="ldap.ad || directory != directoryType.Ldap">
|
||||||
<label for="emailPrefixAttribute">{{'emailPrefixAttribute' | i18n}}</label>
|
<label for="emailPrefixAttribute">{{'emailPrefixAttribute' | i18n}}</label>
|
||||||
<input type="text" class="form-control" id="emailPrefixAttribute"
|
<input type="text" class="form-control" id="emailPrefixAttribute"
|
||||||
name="EmailPrefixAttribute" [(ngModel)]="sync.emailPrefixAttribute">
|
name="EmailPrefixAttribute" [(ngModel)]="sync.emailPrefixAttribute">
|
||||||
@@ -291,12 +369,14 @@
|
|||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="syncGroups" [(ngModel)]="sync.groups"
|
<input class="form-check-input" type="checkbox" id="syncGroups" [(ngModel)]="sync.groups"
|
||||||
name="SyncGroups">
|
name="SyncGroups">
|
||||||
<label class="form-check-label" for="syncGroups">{{'syncGroups' | i18n}}</label>
|
<label class="form-check-label" for="syncGroups">{{(directory !== directoryType.OneLogin ?
|
||||||
|
'syncGroups' : 'syncGroupsOneLogin') | i18n}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div [hidden]="!sync.groups">
|
<div [hidden]="!sync.groups">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="groupFilter">{{'groupFilter' | i18n}}</label>
|
<label for="groupFilter">{{(directory !== directoryType.OneLogin ? 'groupFilter' :
|
||||||
|
'groupFilterOneLogin') | i18n}}</label>
|
||||||
<textarea class="form-control" id="groupFilter" name="GroupFilter"
|
<textarea class="form-control" id="groupFilter" name="GroupFilter"
|
||||||
[(ngModel)]="sync.groupFilter"></textarea>
|
[(ngModel)]="sync.groupFilter"></textarea>
|
||||||
<small class="text-muted form-text" *ngIf="directory === directoryType.Ldap">{{'ex' | i18n}}
|
<small class="text-muted form-text" *ngIf="directory === directoryType.Ldap">{{'ex' | i18n}}
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import {
|
|||||||
OnInit,
|
OnInit,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||||
import { StateService } from 'jslib/abstractions/state.service';
|
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||||
|
|
||||||
import { ProfileOrganizationResponse } from 'jslib/models/response/profileOrganizationResponse';
|
import { ProfileOrganizationResponse } from 'jslib-common/models/response/profileOrganizationResponse';
|
||||||
|
|
||||||
import { ConfigurationService } from '../../services/configuration.service';
|
import { ConfigurationService } from '../../services/configuration.service';
|
||||||
|
|
||||||
@@ -19,6 +19,7 @@ import { AzureConfiguration } from '../../models/azureConfiguration';
|
|||||||
import { GSuiteConfiguration } from '../../models/gsuiteConfiguration';
|
import { GSuiteConfiguration } from '../../models/gsuiteConfiguration';
|
||||||
import { LdapConfiguration } from '../../models/ldapConfiguration';
|
import { LdapConfiguration } from '../../models/ldapConfiguration';
|
||||||
import { OktaConfiguration } from '../../models/oktaConfiguration';
|
import { OktaConfiguration } from '../../models/oktaConfiguration';
|
||||||
|
import { OneLoginConfiguration } from '../../models/oneLoginConfiguration';
|
||||||
import { SyncConfiguration } from '../../models/syncConfiguration';
|
import { SyncConfiguration } from '../../models/syncConfiguration';
|
||||||
|
|
||||||
import { ConnectorUtils } from '../../utils';
|
import { ConnectorUtils } from '../../utils';
|
||||||
@@ -34,10 +35,13 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
|||||||
gsuite = new GSuiteConfiguration();
|
gsuite = new GSuiteConfiguration();
|
||||||
azure = new AzureConfiguration();
|
azure = new AzureConfiguration();
|
||||||
okta = new OktaConfiguration();
|
okta = new OktaConfiguration();
|
||||||
|
oneLogin = new OneLoginConfiguration();
|
||||||
sync = new SyncConfiguration();
|
sync = new SyncConfiguration();
|
||||||
organizationId: string;
|
|
||||||
directoryOptions: any[];
|
directoryOptions: any[];
|
||||||
organizationOptions: any[];
|
showLdapPassword: boolean = false;
|
||||||
|
showAzureKey: boolean = false;
|
||||||
|
showOktaKey: boolean = false;
|
||||||
|
showOneLoginSecret: boolean = false;
|
||||||
|
|
||||||
constructor(private i18nService: I18nService, private configurationService: ConfigurationService,
|
constructor(private i18nService: I18nService, private configurationService: ConfigurationService,
|
||||||
private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone,
|
private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone,
|
||||||
@@ -48,19 +52,11 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
|||||||
{ name: 'Azure Active Directory', value: DirectoryType.AzureActiveDirectory },
|
{ name: 'Azure Active Directory', value: DirectoryType.AzureActiveDirectory },
|
||||||
{ name: 'G Suite (Google)', value: DirectoryType.GSuite },
|
{ name: 'G Suite (Google)', value: DirectoryType.GSuite },
|
||||||
{ name: 'Okta', value: DirectoryType.Okta },
|
{ name: 'Okta', value: DirectoryType.Okta },
|
||||||
|
{ name: 'OneLogin', value: DirectoryType.OneLogin },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.organizationOptions = [{ name: this.i18nService.t('select'), value: null }];
|
|
||||||
const orgs = await this.stateService.get<ProfileOrganizationResponse[]>('profileOrganizations');
|
|
||||||
if (orgs != null) {
|
|
||||||
for (const org of orgs) {
|
|
||||||
this.organizationOptions.push({ name: org.name, value: org.id });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.organizationId = await this.configurationService.getOrganizationId();
|
|
||||||
this.directory = await this.configurationService.getDirectoryType();
|
this.directory = await this.configurationService.getDirectoryType();
|
||||||
this.ldap = (await this.configurationService.getDirectory<LdapConfiguration>(DirectoryType.Ldap)) ||
|
this.ldap = (await this.configurationService.getDirectory<LdapConfiguration>(DirectoryType.Ldap)) ||
|
||||||
this.ldap;
|
this.ldap;
|
||||||
@@ -70,6 +66,8 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
|||||||
DirectoryType.AzureActiveDirectory)) || this.azure;
|
DirectoryType.AzureActiveDirectory)) || this.azure;
|
||||||
this.okta = (await this.configurationService.getDirectory<OktaConfiguration>(
|
this.okta = (await this.configurationService.getDirectory<OktaConfiguration>(
|
||||||
DirectoryType.Okta)) || this.okta;
|
DirectoryType.Okta)) || this.okta;
|
||||||
|
this.oneLogin = (await this.configurationService.getDirectory<OneLoginConfiguration>(
|
||||||
|
DirectoryType.OneLogin)) || this.oneLogin;
|
||||||
this.sync = (await this.configurationService.getSync()) || this.sync;
|
this.sync = (await this.configurationService.getSync()) || this.sync;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,12 +77,15 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
ConnectorUtils.adjustConfigForSave(this.ldap, this.sync);
|
ConnectorUtils.adjustConfigForSave(this.ldap, this.sync);
|
||||||
await this.configurationService.saveOrganizationId(this.organizationId);
|
if (this.ldap != null && this.ldap.ad) {
|
||||||
|
this.ldap.pagedSearch = true;
|
||||||
|
}
|
||||||
await this.configurationService.saveDirectoryType(this.directory);
|
await this.configurationService.saveDirectoryType(this.directory);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.Ldap, this.ldap);
|
await this.configurationService.saveDirectory(DirectoryType.Ldap, this.ldap);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.GSuite, this.gsuite);
|
await this.configurationService.saveDirectory(DirectoryType.GSuite, this.gsuite);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.AzureActiveDirectory, this.azure);
|
await this.configurationService.saveDirectory(DirectoryType.AzureActiveDirectory, this.azure);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.Okta, this.okta);
|
await this.configurationService.saveDirectory(DirectoryType.Okta, this.okta);
|
||||||
|
await this.configurationService.saveDirectory(DirectoryType.OneLogin, this.oneLogin);
|
||||||
await this.configurationService.saveSync(this.sync);
|
await this.configurationService.saveSync(this.sync);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +97,7 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.readAsText(filePicker.files[0], 'utf-8');
|
reader.readAsText(filePicker.files[0], 'utf-8');
|
||||||
reader.onload = (evt) => {
|
reader.onload = evt => {
|
||||||
this.ngZone.run(async () => {
|
this.ngZone.run(async () => {
|
||||||
try {
|
try {
|
||||||
const result = JSON.parse((evt.target as FileReader).result as string);
|
const result = JSON.parse((evt.target as FileReader).result as string);
|
||||||
@@ -129,4 +130,24 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
|||||||
filePicker.type = 'file';
|
filePicker.type = 'file';
|
||||||
filePicker.value = '';
|
filePicker.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleLdapPassword() {
|
||||||
|
this.showLdapPassword = !this.showLdapPassword;
|
||||||
|
document.getElementById('password').focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleAzureKey() {
|
||||||
|
this.showAzureKey = !this.showAzureKey;
|
||||||
|
document.getElementById('secretKey').focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleOktaKey() {
|
||||||
|
this.showOktaKey = !this.showOktaKey;
|
||||||
|
document.getElementById('oktaToken').focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleOneLoginSecret() {
|
||||||
|
this.showOneLoginSecret = !this.showOneLoginSecret;
|
||||||
|
document.getElementById('oneLoginClientSecret').focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
79
src/bwdc.ts
79
src/bwdc.ts
@@ -1,31 +1,36 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { LogLevelType } from 'jslib/enums/logLevelType';
|
import { LogLevelType } from 'jslib-common/enums/logLevelType';
|
||||||
|
|
||||||
import { AuthService } from 'jslib/services/auth.service';
|
import { AuthService } from './services/auth.service';
|
||||||
|
|
||||||
import { ConfigurationService } from './services/configuration.service';
|
import { ConfigurationService } from './services/configuration.service';
|
||||||
import { I18nService } from './services/i18n.service';
|
import { I18nService } from './services/i18n.service';
|
||||||
import { KeytarSecureStorageService } from './services/keytarSecureStorage.service';
|
import { KeytarSecureStorageService } from './services/keytarSecureStorage.service';
|
||||||
|
import { LowdbStorageService } from './services/lowdbStorage.service';
|
||||||
|
import { NodeApiService } from './services/nodeApi.service';
|
||||||
import { SyncService } from './services/sync.service';
|
import { SyncService } from './services/sync.service';
|
||||||
|
|
||||||
import { CliPlatformUtilsService } from 'jslib/cli/services/cliPlatformUtils.service';
|
import { CliPlatformUtilsService } from 'jslib-node/cli/services/cliPlatformUtils.service';
|
||||||
import { ConsoleLogService } from 'jslib/cli/services/consoleLog.service';
|
import { ConsoleLogService } from 'jslib-node/cli/services/consoleLog.service';
|
||||||
|
import { NodeCryptoFunctionService } from 'jslib-node/services/nodeCryptoFunction.service';
|
||||||
|
|
||||||
import { AppIdService } from 'jslib/services/appId.service';
|
import { ApiKeyService } from 'jslib-common/services/apiKey.service';
|
||||||
import { ConstantsService } from 'jslib/services/constants.service';
|
import { AppIdService } from 'jslib-common/services/appId.service';
|
||||||
import { ContainerService } from 'jslib/services/container.service';
|
import { ConstantsService } from 'jslib-common/services/constants.service';
|
||||||
import { CryptoService } from 'jslib/services/crypto.service';
|
import { ContainerService } from 'jslib-common/services/container.service';
|
||||||
import { EnvironmentService } from 'jslib/services/environment.service';
|
import { CryptoService } from 'jslib-common/services/crypto.service';
|
||||||
import { LowdbStorageService } from 'jslib/services/lowdbStorage.service';
|
import { EnvironmentService } from 'jslib-common/services/environment.service';
|
||||||
import { NodeApiService } from 'jslib/services/nodeApi.service';
|
import { NoopMessagingService } from 'jslib-common/services/noopMessaging.service';
|
||||||
import { NodeCryptoFunctionService } from 'jslib/services/nodeCryptoFunction.service';
|
import { PasswordGenerationService } from 'jslib-common/services/passwordGeneration.service';
|
||||||
import { NoopMessagingService } from 'jslib/services/noopMessaging.service';
|
import { TokenService } from 'jslib-common/services/token.service';
|
||||||
import { TokenService } from 'jslib/services/token.service';
|
import { UserService } from 'jslib-common/services/user.service';
|
||||||
import { UserService } from 'jslib/services/user.service';
|
|
||||||
|
import { StorageService as StorageServiceAbstraction } from 'jslib-common/abstractions/storage.service';
|
||||||
|
|
||||||
import { Program } from './program';
|
import { Program } from './program';
|
||||||
|
import { refreshToken } from './services/api.service';
|
||||||
|
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
const packageJson = require('./package.json');
|
const packageJson = require('./package.json');
|
||||||
@@ -35,7 +40,7 @@ export class Main {
|
|||||||
logService: ConsoleLogService;
|
logService: ConsoleLogService;
|
||||||
messagingService: NoopMessagingService;
|
messagingService: NoopMessagingService;
|
||||||
storageService: LowdbStorageService;
|
storageService: LowdbStorageService;
|
||||||
secureStorageService: KeytarSecureStorageService;
|
secureStorageService: StorageServiceAbstraction;
|
||||||
i18nService: I18nService;
|
i18nService: I18nService;
|
||||||
platformUtilsService: CliPlatformUtilsService;
|
platformUtilsService: CliPlatformUtilsService;
|
||||||
constantsService: ConstantsService;
|
constantsService: ConstantsService;
|
||||||
@@ -44,12 +49,14 @@ export class Main {
|
|||||||
appIdService: AppIdService;
|
appIdService: AppIdService;
|
||||||
apiService: NodeApiService;
|
apiService: NodeApiService;
|
||||||
environmentService: EnvironmentService;
|
environmentService: EnvironmentService;
|
||||||
|
apiKeyService: ApiKeyService;
|
||||||
userService: UserService;
|
userService: UserService;
|
||||||
containerService: ContainerService;
|
containerService: ContainerService;
|
||||||
cryptoFunctionService: NodeCryptoFunctionService;
|
cryptoFunctionService: NodeCryptoFunctionService;
|
||||||
authService: AuthService;
|
authService: AuthService;
|
||||||
configurationService: ConfigurationService;
|
configurationService: ConfigurationService;
|
||||||
syncService: SyncService;
|
syncService: SyncService;
|
||||||
|
passwordGenerationService: PasswordGenerationService;
|
||||||
program: Program;
|
program: Program;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -70,28 +77,34 @@ export class Main {
|
|||||||
this.dataFilePath = path.join(process.env.HOME, '.config/' + applicationName);
|
this.dataFilePath = path.join(process.env.HOME, '.config/' + applicationName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const plaintextSecrets = process.env.BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS === 'true';
|
||||||
this.i18nService = new I18nService('en', './locales');
|
this.i18nService = new I18nService('en', './locales');
|
||||||
this.platformUtilsService = new CliPlatformUtilsService('connector', packageJson);
|
this.platformUtilsService = new CliPlatformUtilsService('connector', packageJson);
|
||||||
this.logService = new ConsoleLogService(this.platformUtilsService.isDev(),
|
this.logService = new ConsoleLogService(this.platformUtilsService.isDev(),
|
||||||
(level) => process.env.BWCLI_DEBUG !== 'true' && level <= LogLevelType.Info);
|
level => process.env.BITWARDENCLI_CONNECTOR_DEBUG !== 'true' && level <= LogLevelType.Info);
|
||||||
this.cryptoFunctionService = new NodeCryptoFunctionService();
|
this.cryptoFunctionService = new NodeCryptoFunctionService();
|
||||||
this.storageService = new LowdbStorageService(null, this.dataFilePath, true);
|
this.storageService = new LowdbStorageService(this.logService, null, this.dataFilePath, false, true);
|
||||||
this.secureStorageService = new KeytarSecureStorageService(applicationName);
|
this.secureStorageService = plaintextSecrets ?
|
||||||
|
this.storageService : new KeytarSecureStorageService(applicationName);
|
||||||
this.cryptoService = new CryptoService(this.storageService, this.secureStorageService,
|
this.cryptoService = new CryptoService(this.storageService, this.secureStorageService,
|
||||||
this.cryptoFunctionService);
|
this.cryptoFunctionService, this.platformUtilsService, this.logService);
|
||||||
this.appIdService = new AppIdService(this.storageService);
|
this.appIdService = new AppIdService(this.storageService);
|
||||||
this.tokenService = new TokenService(this.storageService);
|
this.tokenService = new TokenService(this.storageService);
|
||||||
this.messagingService = new NoopMessagingService();
|
this.messagingService = new NoopMessagingService();
|
||||||
this.apiService = new NodeApiService(this.tokenService, this.platformUtilsService,
|
this.environmentService = new EnvironmentService(this.storageService);
|
||||||
async (expired: boolean) => await this.logout());
|
this.apiService = new NodeApiService(this.tokenService, this.platformUtilsService, this.environmentService,
|
||||||
this.environmentService = new EnvironmentService(this.apiService, this.storageService, null);
|
this.refreshTokenCallback, async (expired: boolean) => await this.logout());
|
||||||
|
this.apiKeyService = new ApiKeyService(this.tokenService, this.storageService);
|
||||||
this.userService = new UserService(this.tokenService, this.storageService);
|
this.userService = new UserService(this.tokenService, this.storageService);
|
||||||
this.containerService = new ContainerService(this.cryptoService);
|
this.containerService = new ContainerService(this.cryptoService);
|
||||||
this.authService = new AuthService(this.cryptoService, this.apiService, this.userService, this.tokenService,
|
this.authService = new AuthService(this.cryptoService, this.apiService, this.userService, this.tokenService,
|
||||||
this.appIdService, this.i18nService, this.platformUtilsService, this.messagingService, true);
|
this.appIdService, this.i18nService, this.platformUtilsService, this.messagingService, null,
|
||||||
this.configurationService = new ConfigurationService(this.storageService, this.secureStorageService);
|
this.logService, this.apiKeyService, false);
|
||||||
|
this.configurationService = new ConfigurationService(this.storageService, this.secureStorageService,
|
||||||
|
process.env.BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS !== 'true');
|
||||||
this.syncService = new SyncService(this.configurationService, this.logService, this.cryptoFunctionService,
|
this.syncService = new SyncService(this.configurationService, this.logService, this.cryptoFunctionService,
|
||||||
this.apiService, this.messagingService, this.i18nService);
|
this.apiService, this.messagingService, this.i18nService, this.environmentService);
|
||||||
|
this.passwordGenerationService = new PasswordGenerationService(this.cryptoService, this.storageService, null);
|
||||||
this.program = new Program(this);
|
this.program = new Program(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,14 +114,16 @@ export class Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async logout() {
|
async logout() {
|
||||||
await Promise.all([
|
await this.tokenService.clearToken();
|
||||||
this.tokenService.clearToken(),
|
await this.apiKeyService.clear();
|
||||||
this.userService.clear(),
|
}
|
||||||
]);
|
|
||||||
|
refreshTokenCallback() {
|
||||||
|
return refreshToken(this.apiKeyService, this.authService);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async init() {
|
private async init() {
|
||||||
this.storageService.init();
|
await this.storageService.init();
|
||||||
this.containerService.attachToWindow(global);
|
this.containerService.attachToWindow(global);
|
||||||
await this.environmentService.setUrlsFromStorage();
|
await this.environmentService.setUrlsFromStorage();
|
||||||
// Dev Server URLs. Comment out the line above.
|
// Dev Server URLs. Comment out the line above.
|
||||||
@@ -122,7 +137,7 @@ export class Main {
|
|||||||
this.authService.init();
|
this.authService.init();
|
||||||
|
|
||||||
const installedVersion = await this.storageService.get<string>(ConstantsService.installedVersionKey);
|
const installedVersion = await this.storageService.get<string>(ConstantsService.installedVersionKey);
|
||||||
const currentVersion = this.platformUtilsService.getApplicationVersion();
|
const currentVersion = await this.platformUtilsService.getApplicationVersion();
|
||||||
if (installedVersion == null || installedVersion !== currentVersion) {
|
if (installedVersion == null || installedVersion !== currentVersion) {
|
||||||
await this.storageService.save(ConstantsService.installedVersionKey, currentVersion);
|
await this.storageService.save(ConstantsService.installedVersionKey, currentVersion);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import * as program from 'commander';
|
import * as program from 'commander';
|
||||||
|
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||||
|
|
||||||
import { ConfigurationService } from '../services/configuration.service';
|
import { ConfigurationService } from '../services/configuration.service';
|
||||||
|
|
||||||
import { Response } from 'jslib/cli/models/response';
|
import { Response } from 'jslib-node/cli/models/response';
|
||||||
import { MessageResponse } from 'jslib/cli/models/response/messageResponse';
|
import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse';
|
||||||
|
|
||||||
export class ClearCacheCommand {
|
export class ClearCacheCommand {
|
||||||
constructor(private configurationService: ConfigurationService, private i18nService: I18nService) { }
|
constructor(private configurationService: ConfigurationService, private i18nService: I18nService) { }
|
||||||
|
|
||||||
async run(cmd: program.Command): Promise<Response> {
|
async run(cmd: program.OptionValues): Promise<Response> {
|
||||||
try {
|
try {
|
||||||
await this.configurationService.clearStatefulSettings(true);
|
await this.configurationService.clearStatefulSettings(true);
|
||||||
const res = new MessageResponse(this.i18nService.t('syncCacheCleared'), null);
|
const res = new MessageResponse(this.i18nService.t('syncCacheCleared'), null);
|
||||||
|
|||||||
@@ -1,36 +1,47 @@
|
|||||||
import * as program from 'commander';
|
import * as program from 'commander';
|
||||||
|
|
||||||
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||||
|
|
||||||
import { ConfigurationService } from '../services/configuration.service';
|
import { ConfigurationService } from '../services/configuration.service';
|
||||||
|
|
||||||
import { DirectoryType } from '../enums/directoryType';
|
import { DirectoryType } from '../enums/directoryType';
|
||||||
|
|
||||||
import { Response } from 'jslib/cli/models/response';
|
import { Response } from 'jslib-node/cli/models/response';
|
||||||
import { MessageResponse } from 'jslib/cli/models/response/messageResponse';
|
import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse';
|
||||||
|
|
||||||
import { AzureConfiguration } from '../models/azureConfiguration';
|
import { AzureConfiguration } from '../models/azureConfiguration';
|
||||||
import { GSuiteConfiguration } from '../models/gsuiteConfiguration';
|
import { GSuiteConfiguration } from '../models/gsuiteConfiguration';
|
||||||
import { LdapConfiguration } from '../models/ldapConfiguration';
|
import { LdapConfiguration } from '../models/ldapConfiguration';
|
||||||
import { OktaConfiguration } from '../models/oktaConfiguration';
|
import { OktaConfiguration } from '../models/oktaConfiguration';
|
||||||
|
import { OneLoginConfiguration } from '../models/oneLoginConfiguration';
|
||||||
import { SyncConfiguration } from '../models/syncConfiguration';
|
import { SyncConfiguration } from '../models/syncConfiguration';
|
||||||
|
|
||||||
import { ConnectorUtils } from '../utils';
|
import { ConnectorUtils } from '../utils';
|
||||||
|
|
||||||
|
import { NodeUtils } from 'jslib-common/misc/nodeUtils';
|
||||||
|
|
||||||
export class ConfigCommand {
|
export class ConfigCommand {
|
||||||
private directory: DirectoryType;
|
private directory: DirectoryType;
|
||||||
private ldap = new LdapConfiguration();
|
private ldap = new LdapConfiguration();
|
||||||
private gsuite = new GSuiteConfiguration();
|
private gsuite = new GSuiteConfiguration();
|
||||||
private azure = new AzureConfiguration();
|
private azure = new AzureConfiguration();
|
||||||
private okta = new OktaConfiguration();
|
private okta = new OktaConfiguration();
|
||||||
|
private oneLogin = new OneLoginConfiguration();
|
||||||
private sync = new SyncConfiguration();
|
private sync = new SyncConfiguration();
|
||||||
|
|
||||||
constructor(private environmentService: EnvironmentService, private i18nService: I18nService,
|
constructor(private environmentService: EnvironmentService, private i18nService: I18nService,
|
||||||
private configurationService: ConfigurationService) { }
|
private configurationService: ConfigurationService) { }
|
||||||
|
|
||||||
async run(setting: string, value: string, cmd: program.Command): Promise<Response> {
|
async run(setting: string, value: string, options: program.OptionValues): Promise<Response> {
|
||||||
setting = setting.toLowerCase();
|
setting = setting.toLowerCase();
|
||||||
|
if (value == null || value === '') {
|
||||||
|
if (options.secretfile) {
|
||||||
|
value = await NodeUtils.readFirstLine(options.secretfile);
|
||||||
|
} else if (options.secretenv && process.env[options.secretenv]) {
|
||||||
|
value = process.env[options.secretenv];
|
||||||
|
}
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
switch (setting) {
|
switch (setting) {
|
||||||
case 'server':
|
case 'server':
|
||||||
@@ -51,6 +62,9 @@ export class ConfigCommand {
|
|||||||
case 'okta.token':
|
case 'okta.token':
|
||||||
await this.setOktaToken(value);
|
await this.setOktaToken(value);
|
||||||
break;
|
break;
|
||||||
|
case 'onelogin.secret':
|
||||||
|
await this.setOneLoginSecret(value);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return Response.badRequest('Unknown setting.');
|
return Response.badRequest('Unknown setting.');
|
||||||
}
|
}
|
||||||
@@ -70,7 +84,7 @@ export class ConfigCommand {
|
|||||||
|
|
||||||
private async setDirectory(type: string) {
|
private async setDirectory(type: string) {
|
||||||
const dir = parseInt(type, null);
|
const dir = parseInt(type, null);
|
||||||
if (dir < DirectoryType.Ldap || dir > DirectoryType.Okta) {
|
if (dir < DirectoryType.Ldap || dir > DirectoryType.OneLogin) {
|
||||||
throw new Error('Invalid directory type value.');
|
throw new Error('Invalid directory type value.');
|
||||||
}
|
}
|
||||||
await this.loadConfig();
|
await this.loadConfig();
|
||||||
@@ -102,6 +116,12 @@ export class ConfigCommand {
|
|||||||
await this.saveConfig();
|
await this.saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async setOneLoginSecret(secret: string) {
|
||||||
|
await this.loadConfig();
|
||||||
|
this.oneLogin.clientSecret = secret;
|
||||||
|
await this.saveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
private async loadConfig() {
|
private async loadConfig() {
|
||||||
this.directory = await this.configurationService.getDirectoryType();
|
this.directory = await this.configurationService.getDirectoryType();
|
||||||
this.ldap = (await this.configurationService.getDirectory<LdapConfiguration>(DirectoryType.Ldap)) ||
|
this.ldap = (await this.configurationService.getDirectory<LdapConfiguration>(DirectoryType.Ldap)) ||
|
||||||
@@ -112,6 +132,8 @@ export class ConfigCommand {
|
|||||||
DirectoryType.AzureActiveDirectory)) || this.azure;
|
DirectoryType.AzureActiveDirectory)) || this.azure;
|
||||||
this.okta = (await this.configurationService.getDirectory<OktaConfiguration>(
|
this.okta = (await this.configurationService.getDirectory<OktaConfiguration>(
|
||||||
DirectoryType.Okta)) || this.okta;
|
DirectoryType.Okta)) || this.okta;
|
||||||
|
this.oneLogin = (await this.configurationService.getDirectory<OneLoginConfiguration>(
|
||||||
|
DirectoryType.OneLogin)) || this.oneLogin;
|
||||||
this.sync = (await this.configurationService.getSync()) || this.sync;
|
this.sync = (await this.configurationService.getSync()) || this.sync;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +144,7 @@ export class ConfigCommand {
|
|||||||
await this.configurationService.saveDirectory(DirectoryType.GSuite, this.gsuite);
|
await this.configurationService.saveDirectory(DirectoryType.GSuite, this.gsuite);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.AzureActiveDirectory, this.azure);
|
await this.configurationService.saveDirectory(DirectoryType.AzureActiveDirectory, this.azure);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.Okta, this.okta);
|
await this.configurationService.saveDirectory(DirectoryType.Okta, this.okta);
|
||||||
|
await this.configurationService.saveDirectory(DirectoryType.OneLogin, this.oneLogin);
|
||||||
await this.configurationService.saveSync(this.sync);
|
await this.configurationService.saveSync(this.sync);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import * as program from 'commander';
|
|||||||
|
|
||||||
import { ConfigurationService } from '../services/configuration.service';
|
import { ConfigurationService } from '../services/configuration.service';
|
||||||
|
|
||||||
import { Response } from 'jslib/cli/models/response';
|
import { Response } from 'jslib-node/cli/models/response';
|
||||||
import { StringResponse } from 'jslib/cli/models/response/stringResponse';
|
import { StringResponse } from 'jslib-node/cli/models/response/stringResponse';
|
||||||
|
|
||||||
export class LastSyncCommand {
|
export class LastSyncCommand {
|
||||||
constructor(private configurationService: ConfigurationService) { }
|
constructor(private configurationService: ConfigurationService) { }
|
||||||
|
|
||||||
async run(object: string, cmd: program.Command): Promise<Response> {
|
async run(object: string): Promise<Response> {
|
||||||
try {
|
try {
|
||||||
switch (object.toLowerCase()) {
|
switch (object.toLowerCase()) {
|
||||||
case 'groups':
|
case 'groups':
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import * as program from 'commander';
|
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||||
|
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
|
||||||
|
|
||||||
import { SyncService } from '../services/sync.service';
|
import { SyncService } from '../services/sync.service';
|
||||||
|
|
||||||
import { Response } from 'jslib/cli/models/response';
|
import { Response } from 'jslib-node/cli/models/response';
|
||||||
import { MessageResponse } from 'jslib/cli/models/response/messageResponse';
|
import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse';
|
||||||
|
|
||||||
export class SyncCommand {
|
export class SyncCommand {
|
||||||
constructor(private syncService: SyncService, private i18nService: I18nService) { }
|
constructor(private syncService: SyncService, private i18nService: I18nService) { }
|
||||||
|
|
||||||
async run(cmd: program.Command): Promise<Response> {
|
async run(): Promise<Response> {
|
||||||
try {
|
try {
|
||||||
const result = await this.syncService.sync(false, false);
|
const result = await this.syncService.sync(false, false);
|
||||||
const groupCount = result[0] != null ? result[0].length : 0;
|
const groupCount = result[0] != null ? result[0].length : 0;
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import * as program from 'commander';
|
import * as program from 'commander';
|
||||||
|
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||||
|
|
||||||
import { SyncService } from '../services/sync.service';
|
import { SyncService } from '../services/sync.service';
|
||||||
|
|
||||||
import { ConnectorUtils } from '../utils';
|
import { ConnectorUtils } from '../utils';
|
||||||
|
|
||||||
import { Response } from 'jslib/cli/models/response';
|
import { Response } from 'jslib-node/cli/models/response';
|
||||||
import { TestResponse } from '../models/response/testResponse';
|
import { TestResponse } from '../models/response/testResponse';
|
||||||
|
|
||||||
export class TestCommand {
|
export class TestCommand {
|
||||||
constructor(private syncService: SyncService, private i18nService: I18nService) { }
|
constructor(private syncService: SyncService, private i18nService: I18nService) { }
|
||||||
|
|
||||||
async run(cmd: program.Command): Promise<Response> {
|
async run(cmd: program.OptionValues): Promise<Response> {
|
||||||
try {
|
try {
|
||||||
const result = await ConnectorUtils.simulate(this.syncService, this.i18nService, cmd.last || false);
|
const result = await ConnectorUtils.simulate(this.syncService, this.i18nService, cmd.last || false);
|
||||||
const res = new TestResponse(result);
|
const res = new TestResponse(result);
|
||||||
|
|||||||
@@ -3,4 +3,5 @@ export enum DirectoryType {
|
|||||||
AzureActiveDirectory = 1,
|
AzureActiveDirectory = 1,
|
||||||
GSuite = 2,
|
GSuite = 2,
|
||||||
Okta = 3,
|
Okta = 3,
|
||||||
|
OneLogin = 4,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,12 +20,30 @@
|
|||||||
"emailRequired": {
|
"emailRequired": {
|
||||||
"message": "Email address is required."
|
"message": "Email address is required."
|
||||||
},
|
},
|
||||||
|
"clientIdRequired": {
|
||||||
|
"message": "Client Id is required."
|
||||||
|
},
|
||||||
|
"invalidClientId": {
|
||||||
|
"message": "Invalid Client Id provided."
|
||||||
|
},
|
||||||
|
"clientSecretRequired": {
|
||||||
|
"message": "Client Secret is required."
|
||||||
|
},
|
||||||
|
"orgApiKeyRequired": {
|
||||||
|
"message": "Api Key must belong to an Organization"
|
||||||
|
},
|
||||||
|
"failedToSaveCredentials": {
|
||||||
|
"message": "Failed to save credentials"
|
||||||
|
},
|
||||||
"invalidEmail": {
|
"invalidEmail": {
|
||||||
"message": "Invalid email address."
|
"message": "Invalid email address."
|
||||||
},
|
},
|
||||||
"masterPassRequired": {
|
"masterPassRequired": {
|
||||||
"message": "Master password is required."
|
"message": "Master password is required."
|
||||||
},
|
},
|
||||||
|
"missingRequiredInput": {
|
||||||
|
"message": "Missing required input."
|
||||||
|
},
|
||||||
"unexpectedError": {
|
"unexpectedError": {
|
||||||
"message": "An unexpected error has occurred."
|
"message": "An unexpected error has occurred."
|
||||||
},
|
},
|
||||||
@@ -140,6 +158,9 @@
|
|||||||
"baseUrl": {
|
"baseUrl": {
|
||||||
"message": "Server URL"
|
"message": "Server URL"
|
||||||
},
|
},
|
||||||
|
"webVaultUrl": {
|
||||||
|
"message": "Web Vault Server URL"
|
||||||
|
},
|
||||||
"apiUrl": {
|
"apiUrl": {
|
||||||
"message": "API Server URL"
|
"message": "API Server URL"
|
||||||
},
|
},
|
||||||
@@ -399,6 +420,12 @@
|
|||||||
"groupFilter": {
|
"groupFilter": {
|
||||||
"message": "Group Filter"
|
"message": "Group Filter"
|
||||||
},
|
},
|
||||||
|
"syncGroupsOneLogin": {
|
||||||
|
"message": "Sync roles"
|
||||||
|
},
|
||||||
|
"groupFilterOneLogin": {
|
||||||
|
"message": "Role Filter"
|
||||||
|
},
|
||||||
"groupObjectClass": {
|
"groupObjectClass": {
|
||||||
"message": "Group Object Class"
|
"message": "Group Object Class"
|
||||||
},
|
},
|
||||||
@@ -414,8 +441,33 @@
|
|||||||
"sync": {
|
"sync": {
|
||||||
"message": "Sync"
|
"message": "Sync"
|
||||||
},
|
},
|
||||||
|
"duplicateEmails": {
|
||||||
|
"message": "Emails must be unique. Multiple entries pulled with the following emails:",
|
||||||
|
"desription": "Error message displayed when duplicate email addresses are synced. Followed by a list of duplicate emails."
|
||||||
|
},
|
||||||
|
"andMore": {
|
||||||
|
"message": "and $NUMBER$ more...",
|
||||||
|
"placeholders": {
|
||||||
|
"NUMBER": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ldapEncrypted": {
|
||||||
|
"message": "This server uses an encrypted connection"
|
||||||
|
},
|
||||||
|
"ldapTls": {
|
||||||
|
"message": "Use TLS (STARTTLS)"
|
||||||
|
},
|
||||||
|
"ldapTlsCa": {
|
||||||
|
"message": "Certificate CA Chain (PEM)"
|
||||||
|
},
|
||||||
"ldapSsl": {
|
"ldapSsl": {
|
||||||
"message": "This server uses SSL (LDAPS)"
|
"message": "Use SSL (LDAPS)"
|
||||||
|
},
|
||||||
|
"ldapTlsUntrustedDesc": {
|
||||||
|
"message": "If your LDAP server uses a self-signed certificate for STARTTLS, you can configure certificate options below."
|
||||||
},
|
},
|
||||||
"ldapSslUntrustedDesc": {
|
"ldapSslUntrustedDesc": {
|
||||||
"message": "If your LDAPS server uses an untrusted certificate you can configure certificate options below."
|
"message": "If your LDAPS server uses an untrusted certificate you can configure certificate options below."
|
||||||
@@ -429,12 +481,15 @@
|
|||||||
"ldapSslKey": {
|
"ldapSslKey": {
|
||||||
"message": "Certificate Private Key (PEM)"
|
"message": "Certificate Private Key (PEM)"
|
||||||
},
|
},
|
||||||
"ldapSslAllowUnauthorized": {
|
"ldapCertDoNotVerify": {
|
||||||
"message": "Allow untrusted SSL connections (not recommended)."
|
"message": "Do not verify server certificates (not recommended)."
|
||||||
},
|
},
|
||||||
"ldapAd": {
|
"ldapAd": {
|
||||||
"message": "This server uses Active Directory"
|
"message": "This server uses Active Directory"
|
||||||
},
|
},
|
||||||
|
"ldapPagedResults": {
|
||||||
|
"message": "This server pages search results"
|
||||||
|
},
|
||||||
"select": {
|
"select": {
|
||||||
"message": "Select"
|
"message": "Select"
|
||||||
},
|
},
|
||||||
@@ -551,7 +606,7 @@
|
|||||||
"message": "Welcome to the Bitwarden Directory Connector"
|
"message": "Welcome to the Bitwarden Directory Connector"
|
||||||
},
|
},
|
||||||
"logInDesc": {
|
"logInDesc": {
|
||||||
"message": "Log in as an organization admin user below."
|
"message": "Log in with an organization API key below."
|
||||||
},
|
},
|
||||||
"dirConfigIncomplete": {
|
"dirConfigIncomplete": {
|
||||||
"message": "Directory configuration incomplete."
|
"message": "Directory configuration incomplete."
|
||||||
@@ -570,8 +625,8 @@
|
|||||||
"message": "Hide to Tray"
|
"message": "Hide to Tray"
|
||||||
},
|
},
|
||||||
"alwaysOnTop": {
|
"alwaysOnTop": {
|
||||||
"message": "Always on Top",
|
"message": "Always on Top",
|
||||||
"description": "Application window should always stay on top of other windows"
|
"description": "Application window should always stay on top of other windows"
|
||||||
},
|
},
|
||||||
"hideToMenuBar": {
|
"hideToMenuBar": {
|
||||||
"message": "Hide to Menu Bar"
|
"message": "Hide to Menu Bar"
|
||||||
@@ -585,7 +640,127 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"largeImport": {
|
||||||
|
"message": "More than 2000 users or groups are expected to sync."
|
||||||
|
},
|
||||||
"overwriteExisting": {
|
"overwriteExisting": {
|
||||||
"message": "Overwrite existing organization users based on current sync settings."
|
"message": "Overwrite existing organization users based on current sync settings."
|
||||||
|
},
|
||||||
|
"clientId": {
|
||||||
|
"message": "Client ID"
|
||||||
|
},
|
||||||
|
"clientSecret": {
|
||||||
|
"message": "Client Secret"
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"message": "Region"
|
||||||
|
},
|
||||||
|
"enterpriseSingleSignOn": {
|
||||||
|
"message": "Enterprise Single Sign-On"
|
||||||
|
},
|
||||||
|
"setMasterPassword": {
|
||||||
|
"message": "Set Master Password"
|
||||||
|
},
|
||||||
|
"ssoCompleteRegistration": {
|
||||||
|
"message": "In order to complete logging in with SSO, please set a master password to access and protect your vault."
|
||||||
|
},
|
||||||
|
"newMasterPass": {
|
||||||
|
"message": "New Master Password"
|
||||||
|
},
|
||||||
|
"confirmNewMasterPass": {
|
||||||
|
"message": "Confirm New Master Password"
|
||||||
|
},
|
||||||
|
"masterPasswordPolicyInEffect": {
|
||||||
|
"message": "One or more organization policies require your master password to meet the following requirements:"
|
||||||
|
},
|
||||||
|
"policyInEffectMinComplexity": {
|
||||||
|
"message": "Minimum complexity score of $SCORE$",
|
||||||
|
"placeholders": {
|
||||||
|
"score": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policyInEffectMinLength": {
|
||||||
|
"message": "Minimum length of $LENGTH$",
|
||||||
|
"placeholders": {
|
||||||
|
"length": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "14"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policyInEffectUppercase": {
|
||||||
|
"message": "Contain one or more uppercase characters"
|
||||||
|
},
|
||||||
|
"policyInEffectLowercase": {
|
||||||
|
"message": "Contain one or more lowercase characters"
|
||||||
|
},
|
||||||
|
"policyInEffectNumbers": {
|
||||||
|
"message": "Contain one or more numbers"
|
||||||
|
},
|
||||||
|
"policyInEffectSpecial": {
|
||||||
|
"message": "Contain one or more of the following special characters $CHARS$",
|
||||||
|
"placeholders": {
|
||||||
|
"chars": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "!@#$%^&*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"masterPassDesc": {
|
||||||
|
"message": "The master password is the password you use to access your vault. It is very important that you do not forget your master password. There is no way to recover the password in the event that you forget it."
|
||||||
|
},
|
||||||
|
"reTypeMasterPass": {
|
||||||
|
"message": "Re-type Master Password"
|
||||||
|
},
|
||||||
|
"masterPassHint": {
|
||||||
|
"message": "Master Password Hint (optional)"
|
||||||
|
},
|
||||||
|
"masterPassHintDesc": {
|
||||||
|
"message": "A master password hint can help you remember your password if you forget it."
|
||||||
|
},
|
||||||
|
"strong": {
|
||||||
|
"message": "Strong",
|
||||||
|
"description": "ex. A strong password. Scale: Weak -> Good -> Strong"
|
||||||
|
},
|
||||||
|
"good": {
|
||||||
|
"message": "Good",
|
||||||
|
"description": "ex. A good password. Scale: Weak -> Good -> Strong"
|
||||||
|
},
|
||||||
|
"weak": {
|
||||||
|
"message": "Weak",
|
||||||
|
"description": "ex. A weak password. Scale: Weak -> Good -> Strong"
|
||||||
|
},
|
||||||
|
"weakMasterPassword": {
|
||||||
|
"message": "Weak Master Password"
|
||||||
|
},
|
||||||
|
"weakMasterPasswordDesc": {
|
||||||
|
"message": "The master password you have chosen is weak. You should use a strong master password (or a passphrase) to properly protect your Bitwarden account. Are you sure you want to use this master password?"
|
||||||
|
},
|
||||||
|
"errorOccurred": {
|
||||||
|
"message": "An error has occurred."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"message": "Error"
|
||||||
|
},
|
||||||
|
"masterPassLength": {
|
||||||
|
"message": "Master password must be at least 8 characters long."
|
||||||
|
},
|
||||||
|
"masterPassDoesntMatch": {
|
||||||
|
"message": "Master password confirmation does not match."
|
||||||
|
},
|
||||||
|
"masterPasswordPolicyRequirementsNotMet": {
|
||||||
|
"message": "Your new master password does not meet the policy requirements."
|
||||||
|
},
|
||||||
|
"loading": {
|
||||||
|
"message": "Loading"
|
||||||
|
},
|
||||||
|
"setMasterPasswordRedirect": {
|
||||||
|
"message": "In order to log in with SSO from the Directory Connector, you must first log in through the web vault to set your master password."
|
||||||
|
},
|
||||||
|
"launchWebVault": {
|
||||||
|
"message": "Launch Web Vault"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
43
src/main.ts
43
src/main.ts
@@ -5,13 +5,13 @@ import { MenuMain } from './main/menu.main';
|
|||||||
import { MessagingMain } from './main/messaging.main';
|
import { MessagingMain } from './main/messaging.main';
|
||||||
import { I18nService } from './services/i18n.service';
|
import { I18nService } from './services/i18n.service';
|
||||||
|
|
||||||
import { KeytarStorageListener } from 'jslib/electron/keytarStorageListener';
|
import { KeytarStorageListener } from 'jslib-electron/keytarStorageListener';
|
||||||
import { ElectronLogService } from 'jslib/electron/services/electronLog.service';
|
import { ElectronLogService } from 'jslib-electron/services/electronLog.service';
|
||||||
import { ElectronMainMessagingService } from 'jslib/electron/services/electronMainMessaging.service';
|
import { ElectronMainMessagingService } from 'jslib-electron/services/electronMainMessaging.service';
|
||||||
import { ElectronStorageService } from 'jslib/electron/services/electronStorage.service';
|
import { ElectronStorageService } from 'jslib-electron/services/electronStorage.service';
|
||||||
import { TrayMain } from 'jslib/electron/tray.main';
|
import { TrayMain } from 'jslib-electron/tray.main';
|
||||||
import { UpdaterMain } from 'jslib/electron/updater.main';
|
import { UpdaterMain } from 'jslib-electron/updater.main';
|
||||||
import { WindowMain } from 'jslib/electron/window.main';
|
import { WindowMain } from 'jslib-electron/window.main';
|
||||||
|
|
||||||
export class Main {
|
export class Main {
|
||||||
logService: ElectronLogService;
|
logService: ElectronLogService;
|
||||||
@@ -41,7 +41,7 @@ export class Main {
|
|||||||
app.setPath('logs', path.join(app.getPath('userData'), 'logs'));
|
app.setPath('logs', path.join(app.getPath('userData'), 'logs'));
|
||||||
|
|
||||||
const args = process.argv.slice(1);
|
const args = process.argv.slice(1);
|
||||||
const watch = args.some((val) => val === '--watch');
|
const watch = args.some(val => val === '--watch');
|
||||||
|
|
||||||
if (watch) {
|
if (watch) {
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
@@ -52,7 +52,7 @@ export class Main {
|
|||||||
this.i18nService = new I18nService('en', './locales/');
|
this.i18nService = new I18nService('en', './locales/');
|
||||||
this.storageService = new ElectronStorageService(app.getPath('userData'));
|
this.storageService = new ElectronStorageService(app.getPath('userData'));
|
||||||
|
|
||||||
this.windowMain = new WindowMain(this.storageService, false, 800, 600);
|
this.windowMain = new WindowMain(this.storageService, false, 800, 600, arg => this.processDeepLink(arg), null);
|
||||||
this.menuMain = new MenuMain(this);
|
this.menuMain = new MenuMain(this);
|
||||||
this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain, 'directory-connector', () => {
|
this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain, 'directory-connector', () => {
|
||||||
this.messagingService.send('checkingForUpdate');
|
this.messagingService.send('checkingForUpdate');
|
||||||
@@ -63,11 +63,11 @@ export class Main {
|
|||||||
}, 'bitwardenDirectoryConnector');
|
}, 'bitwardenDirectoryConnector');
|
||||||
this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.storageService);
|
this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.storageService);
|
||||||
this.messagingMain = new MessagingMain(this.windowMain, this.menuMain, this.updaterMain, this.trayMain);
|
this.messagingMain = new MessagingMain(this.windowMain, this.menuMain, this.updaterMain, this.trayMain);
|
||||||
this.messagingService = new ElectronMainMessagingService(this.windowMain, (message) => {
|
this.messagingService = new ElectronMainMessagingService(this.windowMain, message => {
|
||||||
this.messagingMain.onMessage(message);
|
this.messagingMain.onMessage(message);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.keytarStorageListener = new KeytarStorageListener('Bitwarden Directory Connector');
|
this.keytarStorageListener = new KeytarStorageListener('Bitwarden Directory Connector', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
bootstrap() {
|
bootstrap() {
|
||||||
@@ -78,11 +78,32 @@ export class Main {
|
|||||||
this.messagingMain.init();
|
this.messagingMain.init();
|
||||||
await this.updaterMain.init();
|
await this.updaterMain.init();
|
||||||
await this.trayMain.init(this.i18nService.t('bitwardenDirectoryConnector'));
|
await this.trayMain.init(this.i18nService.t('bitwardenDirectoryConnector'));
|
||||||
|
|
||||||
|
if (!app.isDefaultProtocolClient('bwdc')) {
|
||||||
|
app.setAsDefaultProtocolClient('bwdc');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process protocol for macOS
|
||||||
|
app.on('open-url', (event, url) => {
|
||||||
|
event.preventDefault();
|
||||||
|
this.processDeepLink([url]);
|
||||||
|
});
|
||||||
}, (e: any) => {
|
}, (e: any) => {
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
console.error(e);
|
console.error(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private processDeepLink(argv: string[]): void {
|
||||||
|
argv.filter(s => s.indexOf('bwdc://') === 0).forEach(s => {
|
||||||
|
const url = new URL(s);
|
||||||
|
const code = url.searchParams.get('code');
|
||||||
|
const receivedState = url.searchParams.get('state');
|
||||||
|
if (code != null && receivedState != null) {
|
||||||
|
this.messagingService.send('ssoCallback', { code: code, state: receivedState });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const main = new Main();
|
const main = new Main();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
|
|
||||||
import { Main } from '../main';
|
import { Main } from '../main';
|
||||||
|
|
||||||
import { BaseMenu } from 'jslib/electron/baseMenu';
|
import { BaseMenu } from 'jslib-electron/baseMenu';
|
||||||
|
|
||||||
export class MenuMain extends BaseMenu {
|
export class MenuMain extends BaseMenu {
|
||||||
menu: Menu;
|
menu: Menu;
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import {
|
|||||||
ipcMain,
|
ipcMain,
|
||||||
} from 'electron';
|
} from 'electron';
|
||||||
|
|
||||||
import { TrayMain } from 'jslib/electron/tray.main';
|
import { TrayMain } from 'jslib-electron/tray.main';
|
||||||
import { UpdaterMain } from 'jslib/electron/updater.main';
|
import { UpdaterMain } from 'jslib-electron/updater.main';
|
||||||
import { WindowMain } from 'jslib/electron/window.main';
|
import { WindowMain } from 'jslib-electron/window.main';
|
||||||
|
|
||||||
import { MenuMain } from './menu.main';
|
import { MenuMain } from './menu.main';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
export class LdapConfiguration {
|
export class LdapConfiguration {
|
||||||
ssl = false;
|
ssl = false;
|
||||||
|
startTls = false;
|
||||||
|
tlsCaPath: string;
|
||||||
sslAllowUnauthorized = false;
|
sslAllowUnauthorized = false;
|
||||||
sslCertPath: string;
|
sslCertPath: string;
|
||||||
sslKeyPath: string;
|
sslKeyPath: string;
|
||||||
@@ -12,4 +14,5 @@ export class LdapConfiguration {
|
|||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
ad = true;
|
ad = true;
|
||||||
|
pagedSearch = true;
|
||||||
}
|
}
|
||||||
|
|||||||
5
src/models/oneLoginConfiguration.ts
Normal file
5
src/models/oneLoginConfiguration.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export class OneLoginConfiguration {
|
||||||
|
clientId: string;
|
||||||
|
clientSecret: string;
|
||||||
|
region = 'us';
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import { UserResponse } from './userResponse';
|
|||||||
|
|
||||||
import { SimResult } from '../simResult';
|
import { SimResult } from '../simResult';
|
||||||
|
|
||||||
import { BaseResponse } from 'jslib/cli/models/response/baseResponse';
|
import { BaseResponse } from 'jslib-node/cli/models/response/baseResponse';
|
||||||
|
|
||||||
export class TestResponse implements BaseResponse {
|
export class TestResponse implements BaseResponse {
|
||||||
object: string;
|
object: string;
|
||||||
@@ -14,9 +14,9 @@ export class TestResponse implements BaseResponse {
|
|||||||
|
|
||||||
constructor(result: SimResult) {
|
constructor(result: SimResult) {
|
||||||
this.object = 'test';
|
this.object = 'test';
|
||||||
this.groups = result.groups != null ? result.groups.map((g) => new GroupResponse(g)) : [];
|
this.groups = result.groups != null ? result.groups.map(g => new GroupResponse(g)) : [];
|
||||||
this.enabledUsers = result.enabledUsers != null ? result.enabledUsers.map((u) => new UserResponse(u)) : [];
|
this.enabledUsers = result.enabledUsers != null ? result.enabledUsers.map(u => new UserResponse(u)) : [];
|
||||||
this.disabledUsers = result.disabledUsers != null ? result.disabledUsers.map((u) => new UserResponse(u)) : [];
|
this.disabledUsers = result.disabledUsers != null ? result.disabledUsers.map(u => new UserResponse(u)) : [];
|
||||||
this.deletedUsers = result.deletedUsers != null ? result.deletedUsers.map((u) => new UserResponse(u)) : [];
|
this.deletedUsers = result.deletedUsers != null ? result.deletedUsers.map(u => new UserResponse(u)) : [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export class SyncConfiguration {
|
|||||||
groupFilter: string;
|
groupFilter: string;
|
||||||
removeDisabled = false;
|
removeDisabled = false;
|
||||||
overwriteExisting = false;
|
overwriteExisting = false;
|
||||||
|
largeImport = false;
|
||||||
// Ldap properties
|
// Ldap properties
|
||||||
groupObjectClass: string;
|
groupObjectClass: string;
|
||||||
userObjectClass: string;
|
userObjectClass: string;
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
"name": "bitwarden-directory-connector",
|
"name": "bitwarden-directory-connector",
|
||||||
"productName": "Bitwarden Directory Connector",
|
"productName": "Bitwarden Directory Connector",
|
||||||
"description": "Sync your user directory to your Bitwarden organization.",
|
"description": "Sync your user directory to your Bitwarden organization.",
|
||||||
"version": "2.6.1",
|
"version": "2.9.4",
|
||||||
"author": "8bit Solutions LLC <hello@bitwarden.com> (https://bitwarden.com)",
|
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
|
||||||
"homepage": "https://bitwarden.com",
|
"homepage": "https://bitwarden.com",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
@@ -12,9 +12,10 @@
|
|||||||
"url": "https://github.com/bitwarden/directory-connector"
|
"url": "https://github.com/bitwarden/directory-connector"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"electron-log": "2.2.17",
|
"browser-hrtime": "^1.1.8",
|
||||||
"electron-store": "1.3.0",
|
"electron-log": "4.3.5",
|
||||||
"electron-updater": "4.0.6",
|
"electron-store": "8.0.0",
|
||||||
"keytar": "4.4.1"
|
"electron-updater": "4.3.9",
|
||||||
|
"keytar": "7.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
124
src/program.ts
124
src/program.ts
@@ -1,4 +1,4 @@
|
|||||||
import * as chk from 'chalk';
|
import * as chalk from 'chalk';
|
||||||
import * as program from 'commander';
|
import * as program from 'commander';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
@@ -10,16 +10,18 @@ import { LastSyncCommand } from './commands/lastSync.command';
|
|||||||
import { SyncCommand } from './commands/sync.command';
|
import { SyncCommand } from './commands/sync.command';
|
||||||
import { TestCommand } from './commands/test.command';
|
import { TestCommand } from './commands/test.command';
|
||||||
|
|
||||||
import { LoginCommand } from 'jslib/cli/commands/login.command';
|
import { LoginCommand } from 'jslib-node/cli/commands/login.command';
|
||||||
import { LogoutCommand } from 'jslib/cli/commands/logout.command';
|
import { LogoutCommand } from 'jslib-node/cli/commands/logout.command';
|
||||||
import { UpdateCommand } from 'jslib/cli/commands/update.command';
|
import { UpdateCommand } from 'jslib-node/cli/commands/update.command';
|
||||||
|
|
||||||
import { BaseProgram } from 'jslib/cli/baseProgram';
|
import { BaseProgram } from 'jslib-node/cli/baseProgram';
|
||||||
|
|
||||||
import { Response } from 'jslib/cli/models/response';
|
import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service';
|
||||||
import { StringResponse } from 'jslib/cli/models/response/stringResponse';
|
import { Response } from 'jslib-node/cli/models/response';
|
||||||
|
import { StringResponse } from 'jslib-node/cli/models/response/stringResponse';
|
||||||
|
|
||||||
|
import { Utils } from 'jslib-common/misc/utils';
|
||||||
|
|
||||||
const chalk = chk.default;
|
|
||||||
const writeLn = (s: string, finalLine: boolean = false, error: boolean = false) => {
|
const writeLn = (s: string, finalLine: boolean = false, error: boolean = false) => {
|
||||||
const stream = error ? process.stderr : process.stdout;
|
const stream = error ? process.stderr : process.stdout;
|
||||||
if (finalLine && process.platform === 'win32') {
|
if (finalLine && process.platform === 'win32') {
|
||||||
@@ -30,17 +32,22 @@ const writeLn = (s: string, finalLine: boolean = false, error: boolean = false)
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class Program extends BaseProgram {
|
export class Program extends BaseProgram {
|
||||||
|
private apiKeyService: ApiKeyService;
|
||||||
|
|
||||||
constructor(private main: Main) {
|
constructor(private main: Main) {
|
||||||
super(main.userService, writeLn);
|
super(main.userService, writeLn);
|
||||||
|
this.apiKeyService = main.apiKeyService;
|
||||||
}
|
}
|
||||||
|
|
||||||
run() {
|
async run() {
|
||||||
program
|
program
|
||||||
.option('--pretty', 'Format output. JSON is tabbed with two spaces.')
|
.option('--pretty', 'Format output. JSON is tabbed with two spaces.')
|
||||||
.option('--raw', 'Return raw output instead of a descriptive message.')
|
.option('--raw', 'Return raw output instead of a descriptive message.')
|
||||||
.option('--response', 'Return a JSON formatted version of response output.')
|
.option('--response', 'Return a JSON formatted version of response output.')
|
||||||
|
.option('--cleanexit', 'Exit with a success exit code (0) unless an error is thrown.')
|
||||||
.option('--quiet', 'Don\'t return anything to stdout.')
|
.option('--quiet', 'Don\'t return anything to stdout.')
|
||||||
.version(this.main.platformUtilsService.getApplicationVersion(), '-v, --version');
|
.option('--nointeraction', 'Do not prompt for interactive user input.')
|
||||||
|
.version(await this.main.platformUtilsService.getApplicationVersion(), '-v, --version');
|
||||||
|
|
||||||
program.on('option:pretty', () => {
|
program.on('option:pretty', () => {
|
||||||
process.env.BW_PRETTY = 'true';
|
process.env.BW_PRETTY = 'true';
|
||||||
@@ -58,6 +65,14 @@ export class Program extends BaseProgram {
|
|||||||
process.env.BW_RESPONSE = 'true';
|
process.env.BW_RESPONSE = 'true';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
program.on('option:cleanexit', () => {
|
||||||
|
process.env.BW_CLEANEXIT = 'true';
|
||||||
|
});
|
||||||
|
|
||||||
|
program.on('option:nointeraction', () => {
|
||||||
|
process.env.BW_NOINTERACTION = 'true';
|
||||||
|
});
|
||||||
|
|
||||||
program.on('command:*', () => {
|
program.on('command:*', () => {
|
||||||
writeLn(chalk.redBright('Invalid command: ' + program.args.join(' ')), false, true);
|
writeLn(chalk.redBright('Invalid command: ' + program.args.join(' ')), false, true);
|
||||||
writeLn('See --help for a list of available commands.', true, true);
|
writeLn('See --help for a list of available commands.', true, true);
|
||||||
@@ -77,26 +92,26 @@ export class Program extends BaseProgram {
|
|||||||
});
|
});
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('login [email] [password]')
|
.command('login [clientId] [clientSecret]')
|
||||||
.description('Log into a user account.')
|
.description('Log into an organization account.', {
|
||||||
.option('--method <method>', 'Two-step login method.')
|
clientId: 'Client_id part of your organization\'s API key',
|
||||||
.option('--code <code>', 'Two-step login code.')
|
clientSecret: 'Client_secret part of your organization\'s API key',
|
||||||
.on('--help', () => {
|
|
||||||
writeLn('\n Notes:');
|
|
||||||
writeLn('');
|
|
||||||
writeLn(' See docs for valid `method` enum values.');
|
|
||||||
writeLn('');
|
|
||||||
writeLn(' Examples:');
|
|
||||||
writeLn('');
|
|
||||||
writeLn(' bw login');
|
|
||||||
writeLn(' bw login john@example.com myPassword321');
|
|
||||||
writeLn(' bw login john@example.com myPassword321 --method 1 --code 249213');
|
|
||||||
writeLn('', true);
|
|
||||||
})
|
})
|
||||||
.action(async (email: string, password: string, cmd: program.Command) => {
|
.action(async (clientId: string, clientSecret: string, options: program.OptionValues) => {
|
||||||
await this.exitIfAuthed();
|
await this.exitIfAuthed();
|
||||||
const command = new LoginCommand(this.main.authService, this.main.apiService, this.main.i18nService);
|
const command = new LoginCommand(this.main.authService, this.main.apiService, this.main.i18nService,
|
||||||
const response = await command.run(email, password, cmd);
|
this.main.environmentService, this.main.passwordGenerationService, this.main.cryptoFunctionService,
|
||||||
|
this.main.platformUtilsService, 'connector');
|
||||||
|
|
||||||
|
if (!Utils.isNullOrWhitespace(clientId)) {
|
||||||
|
process.env.BW_CLIENTID = clientId;
|
||||||
|
}
|
||||||
|
if (!Utils.isNullOrWhitespace(clientSecret)) {
|
||||||
|
process.env.BW_CLIENTSECRET = clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
options = Object.assign(options ?? {}, { apikey: true }); // force apikey use
|
||||||
|
const response = await command.run(null, null, options);
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -106,14 +121,14 @@ export class Program extends BaseProgram {
|
|||||||
.on('--help', () => {
|
.on('--help', () => {
|
||||||
writeLn('\n Examples:');
|
writeLn('\n Examples:');
|
||||||
writeLn('');
|
writeLn('');
|
||||||
writeLn(' bw logout');
|
writeLn(' bwdc logout');
|
||||||
writeLn('', true);
|
writeLn('', true);
|
||||||
})
|
})
|
||||||
.action(async (cmd) => {
|
.action(async () => {
|
||||||
await this.exitIfNotAuthed();
|
await this.exitIfNotAuthed();
|
||||||
const command = new LogoutCommand(this.main.authService, this.main.i18nService,
|
const command = new LogoutCommand(this.main.authService, this.main.i18nService,
|
||||||
async () => await this.main.logout());
|
async () => await this.main.logout());
|
||||||
const response = await command.run(cmd);
|
const response = await command.run();
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -128,10 +143,10 @@ export class Program extends BaseProgram {
|
|||||||
writeLn(' bwdc test --last');
|
writeLn(' bwdc test --last');
|
||||||
writeLn('', true);
|
writeLn('', true);
|
||||||
})
|
})
|
||||||
.action(async (cmd) => {
|
.action(async (options: program.OptionValues) => {
|
||||||
await this.exitIfNotAuthed();
|
await this.exitIfNotAuthed();
|
||||||
const command = new TestCommand(this.main.syncService, this.main.i18nService);
|
const command = new TestCommand(this.main.syncService, this.main.i18nService);
|
||||||
const response = await command.run(cmd);
|
const response = await command.run(options);
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -144,10 +159,10 @@ export class Program extends BaseProgram {
|
|||||||
writeLn(' bwdc sync');
|
writeLn(' bwdc sync');
|
||||||
writeLn('', true);
|
writeLn('', true);
|
||||||
})
|
})
|
||||||
.action(async (cmd) => {
|
.action(async () => {
|
||||||
await this.exitIfNotAuthed();
|
await this.exitIfNotAuthed();
|
||||||
const command = new SyncCommand(this.main.syncService, this.main.i18nService);
|
const command = new SyncCommand(this.main.syncService, this.main.i18nService);
|
||||||
const response = await command.run(cmd);
|
const response = await command.run();
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -165,16 +180,18 @@ export class Program extends BaseProgram {
|
|||||||
writeLn(' bwdc last-sync users');
|
writeLn(' bwdc last-sync users');
|
||||||
writeLn('', true);
|
writeLn('', true);
|
||||||
})
|
})
|
||||||
.action(async (object: string, cmd: program.Command) => {
|
.action(async (object: string) => {
|
||||||
await this.exitIfNotAuthed();
|
await this.exitIfNotAuthed();
|
||||||
const command = new LastSyncCommand(this.main.configurationService);
|
const command = new LastSyncCommand(this.main.configurationService);
|
||||||
const response = await command.run(object, cmd);
|
const response = await command.run(object);
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('config <setting> <value>')
|
.command('config <setting> [value]')
|
||||||
.description('Configure settings.')
|
.description('Configure settings.')
|
||||||
|
.option('--secretenv <variable-name>', 'Read secret from the named environment variable.')
|
||||||
|
.option('--secretfile <filename>', 'Read secret from first line of the named file.')
|
||||||
.on('--help', () => {
|
.on('--help', () => {
|
||||||
writeLn('\n Settings:');
|
writeLn('\n Settings:');
|
||||||
writeLn('');
|
writeLn('');
|
||||||
@@ -184,6 +201,7 @@ export class Program extends BaseProgram {
|
|||||||
writeLn(' azure.key - The Azure AD secret key.');
|
writeLn(' azure.key - The Azure AD secret key.');
|
||||||
writeLn(' gsuite.key - The G Suite private key.');
|
writeLn(' gsuite.key - The G Suite private key.');
|
||||||
writeLn(' okta.token - The Okta token.');
|
writeLn(' okta.token - The Okta token.');
|
||||||
|
writeLn(' onelogin.secret - The OneLogin client secret.');
|
||||||
writeLn('');
|
writeLn('');
|
||||||
writeLn(' Examples:');
|
writeLn(' Examples:');
|
||||||
writeLn('');
|
writeLn('');
|
||||||
@@ -191,15 +209,17 @@ export class Program extends BaseProgram {
|
|||||||
writeLn(' bwdc config server bitwarden.com');
|
writeLn(' bwdc config server bitwarden.com');
|
||||||
writeLn(' bwdc config directory 1');
|
writeLn(' bwdc config directory 1');
|
||||||
writeLn(' bwdc config ldap.password <password>');
|
writeLn(' bwdc config ldap.password <password>');
|
||||||
|
writeLn(' bwdc config ldap.password --secretenv LDAP_PWD');
|
||||||
writeLn(' bwdc config azure.key <key>');
|
writeLn(' bwdc config azure.key <key>');
|
||||||
writeLn(' bwdc config gsuite.key <key>');
|
writeLn(' bwdc config gsuite.key <key>');
|
||||||
writeLn(' bwdc config okta.token <token>');
|
writeLn(' bwdc config okta.token <token>');
|
||||||
|
writeLn(' bwdc config onelogin.secret <secret>');
|
||||||
writeLn('', true);
|
writeLn('', true);
|
||||||
})
|
})
|
||||||
.action(async (setting, value, cmd) => {
|
.action(async (setting: string, value: string, options: program.OptionValues) => {
|
||||||
const command = new ConfigCommand(this.main.environmentService, this.main.i18nService,
|
const command = new ConfigCommand(this.main.environmentService, this.main.i18nService,
|
||||||
this.main.configurationService);
|
this.main.configurationService);
|
||||||
const response = await command.run(setting, value, cmd);
|
const response = await command.run(setting, value, options);
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -226,9 +246,9 @@ export class Program extends BaseProgram {
|
|||||||
writeLn(' bwdc clear-cache');
|
writeLn(' bwdc clear-cache');
|
||||||
writeLn('', true);
|
writeLn('', true);
|
||||||
})
|
})
|
||||||
.action(async (cmd) => {
|
.action(async (options: program.OptionValues) => {
|
||||||
const command = new ClearCacheCommand(this.main.configurationService, this.main.i18nService);
|
const command = new ClearCacheCommand(this.main.configurationService, this.main.i18nService);
|
||||||
const response = await command.run(cmd);
|
const response = await command.run(options);
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -248,10 +268,10 @@ export class Program extends BaseProgram {
|
|||||||
writeLn(' bwdc update --raw');
|
writeLn(' bwdc update --raw');
|
||||||
writeLn('', true);
|
writeLn('', true);
|
||||||
})
|
})
|
||||||
.action(async (cmd) => {
|
.action(async () => {
|
||||||
const command = new UpdateCommand(this.main.platformUtilsService, this.main.i18nService,
|
const command = new UpdateCommand(this.main.platformUtilsService, this.main.i18nService,
|
||||||
'directory-connector', 'bwdc', false);
|
'directory-connector', 'bwdc', false);
|
||||||
const response = await command.run(cmd);
|
const response = await command.run();
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -262,4 +282,20 @@ export class Program extends BaseProgram {
|
|||||||
program.outputHelp();
|
program.outputHelp();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async exitIfAuthed() {
|
||||||
|
const authed = await this.apiKeyService.isAuthenticated();
|
||||||
|
if (authed) {
|
||||||
|
const type = await this.apiKeyService.getEntityType();
|
||||||
|
const id = await this.apiKeyService.getEntityId();
|
||||||
|
this.processResponse(Response.error('You are already logged in as ' + type + '.' + id + '.'), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async exitIfNotAuthed() {
|
||||||
|
const authed = await this.apiKeyService.isAuthenticated();
|
||||||
|
if (!authed) {
|
||||||
|
this.processResponse(Response.error('You are not logged in.'), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/scss/bootstrap.scss
vendored
11
src/scss/bootstrap.scss
vendored
@@ -1,4 +1,4 @@
|
|||||||
$theme-colors: ( "primary": #3c8dbc, "primary-accent": #286090, "danger": #dd4b39, "success": #00a65a, "info": #555555, "warning": #bf7e16);
|
$theme-colors: ( "primary": #175DDC, "primary-accent": #1252A3, "danger": #dd4b39, "success": #00a65a, "info": #555555, "warning": #bf7e16, "secondary": #ced4da, "secondary-alt": #1A3B66);
|
||||||
$font-family-sans-serif: 'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
$font-family-sans-serif: 'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||||
|
|
||||||
$h1-font-size: 2rem;
|
$h1-font-size: 2rem;
|
||||||
@@ -8,4 +8,13 @@ $h4-font-size: 1rem;
|
|||||||
$h5-font-size: 1rem;
|
$h5-font-size: 1rem;
|
||||||
$h6-font-size: 1rem;
|
$h6-font-size: 1rem;
|
||||||
|
|
||||||
|
$primary: map_get($theme-colors, 'primary');
|
||||||
|
$primary-accent: map_get($theme-colors, 'primary-accent');
|
||||||
|
$success: map_get($theme-colors, 'success');
|
||||||
|
$info: map_get($theme-colors, 'info');
|
||||||
|
$warning: map_get($theme-colors, 'warning');
|
||||||
|
$danger: map_get($theme-colors, 'danger');
|
||||||
|
$secondary: map_get($theme-colors, 'secondary');
|
||||||
|
$secondary-alt: map_get($theme-colors, 'secondary-alt');
|
||||||
|
|
||||||
@import "~bootstrap/scss/bootstrap.scss";
|
@import "~bootstrap/scss/bootstrap.scss";
|
||||||
|
|||||||
@@ -53,3 +53,92 @@ ul.testing-list {
|
|||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.callout {
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border: 1px solid #000000;
|
||||||
|
border-left-width: 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border-color: #ddd;
|
||||||
|
background-color: white;
|
||||||
|
|
||||||
|
.callout-heading {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3.callout-heading {
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.callout-primary {
|
||||||
|
border-left-color: $primary;
|
||||||
|
|
||||||
|
.callout-heading {
|
||||||
|
color: $primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.callout-info {
|
||||||
|
border-left-color: $info;
|
||||||
|
|
||||||
|
.callout-heading {
|
||||||
|
color: $info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.callout-danger {
|
||||||
|
border-left-color: $danger;
|
||||||
|
|
||||||
|
.callout-heading {
|
||||||
|
color: $danger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.callout-success {
|
||||||
|
border-left-color: $success;
|
||||||
|
|
||||||
|
.callout-heading {
|
||||||
|
color: $success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.callout-warning {
|
||||||
|
border-left-color: $warning;
|
||||||
|
|
||||||
|
.callout-heading {
|
||||||
|
color: $warning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding-left: 40px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn[class*="btn-outline-"] {
|
||||||
|
&:not(:hover) {
|
||||||
|
border-color: $secondary;
|
||||||
|
background-color: #fbfbfb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-secondary {
|
||||||
|
color: $text-muted;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
color: $body-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus,
|
||||||
|
&.focus {
|
||||||
|
box-shadow: 0 0 0 $btn-focus-width rgba(mix(color-yiq($primary), $primary, 15%), .5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
31
src/services/api.service.ts
Normal file
31
src/services/api.service.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service';
|
||||||
|
import { AuthService } from 'jslib-common/abstractions/auth.service';
|
||||||
|
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
||||||
|
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||||
|
import { TokenService } from 'jslib-common/abstractions/token.service';
|
||||||
|
|
||||||
|
import { ApiService as ApiServiceBase } from 'jslib-common/services/api.service';
|
||||||
|
|
||||||
|
export async function refreshToken(apiKeyService: ApiKeyService, authService: AuthService) {
|
||||||
|
try {
|
||||||
|
const clientId = await apiKeyService.getClientId();
|
||||||
|
const clientSecret = await apiKeyService.getClientSecret();
|
||||||
|
if (clientId != null && clientSecret != null) {
|
||||||
|
await authService.logInApiKey(clientId, clientSecret);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return Promise.reject(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ApiService extends ApiServiceBase {
|
||||||
|
constructor(tokenService: TokenService, platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
|
||||||
|
private refreshTokenCallback: () => Promise<void>, logoutCallback: (expired: boolean) => Promise<void>,
|
||||||
|
customUserAgent: string = null) {
|
||||||
|
super(tokenService, platformUtilsService, environmentService, logoutCallback, customUserAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
doRefreshToken(): Promise<void> {
|
||||||
|
return this.refreshTokenCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/services/auth.service.ts
Normal file
61
src/services/auth.service.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { ApiService } from 'jslib-common/abstractions/api.service';
|
||||||
|
import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service';
|
||||||
|
import { AppIdService } from 'jslib-common/abstractions/appId.service';
|
||||||
|
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
||||||
|
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||||
|
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||||
|
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||||
|
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||||
|
import { TokenService } from 'jslib-common/abstractions/token.service';
|
||||||
|
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||||
|
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
||||||
|
|
||||||
|
import { AuthService as AuthServiceBase } from 'jslib-common/services/auth.service';
|
||||||
|
|
||||||
|
import { AuthResult } from 'jslib-common/models/domain';
|
||||||
|
import { DeviceRequest } from 'jslib-common/models/request/deviceRequest';
|
||||||
|
import { TokenRequest } from 'jslib-common/models/request/tokenRequest';
|
||||||
|
import { IdentityTokenResponse } from 'jslib-common/models/response/identityTokenResponse';
|
||||||
|
|
||||||
|
export class AuthService extends AuthServiceBase {
|
||||||
|
|
||||||
|
constructor(cryptoService: CryptoService, apiService: ApiService, userService: UserService,
|
||||||
|
tokenService: TokenService, appIdService: AppIdService, i18nService: I18nService,
|
||||||
|
platformUtilsService: PlatformUtilsService, messagingService: MessagingService,
|
||||||
|
vaultTimeoutService: VaultTimeoutService, logService: LogService, private apiKeyService: ApiKeyService,
|
||||||
|
setCryptoKeys = true) {
|
||||||
|
super(cryptoService, apiService, userService, tokenService, appIdService, i18nService, platformUtilsService,
|
||||||
|
messagingService, vaultTimeoutService, logService, setCryptoKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
async logInApiKey(clientId: string, clientSecret: string): Promise<AuthResult> {
|
||||||
|
this.selectedTwoFactorProviderType = null;
|
||||||
|
if (clientId.startsWith('organization')) {
|
||||||
|
return await this.organizationLogInHelper(clientId, clientSecret);
|
||||||
|
}
|
||||||
|
return await super.logInApiKey(clientId, clientSecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
async logOut(callback: Function) {
|
||||||
|
this.apiKeyService.clear();
|
||||||
|
super.logOut(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async organizationLogInHelper(clientId: string, clientSecret: string) {
|
||||||
|
const appId = await this.appIdService.getAppId();
|
||||||
|
const deviceRequest = new DeviceRequest(appId, this.platformUtilsService);
|
||||||
|
const request = new TokenRequest(null, null, [clientId, clientSecret], null,
|
||||||
|
null, false, null, deviceRequest);
|
||||||
|
|
||||||
|
const response = await this.apiService.postIdentityToken(request);
|
||||||
|
const result = new AuthResult();
|
||||||
|
result.twoFactor = !(response as any).accessToken;
|
||||||
|
|
||||||
|
const tokenResponse = response as IdentityTokenResponse;
|
||||||
|
result.resetMasterPassword = tokenResponse.resetMasterPassword;
|
||||||
|
await this.tokenService.setToken(tokenResponse.accessToken);
|
||||||
|
await this.apiKeyService.setInformation(clientId, clientSecret);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,14 +12,15 @@ import { UserEntry } from '../models/userEntry';
|
|||||||
|
|
||||||
import { BaseDirectoryService } from './baseDirectory.service';
|
import { BaseDirectoryService } from './baseDirectory.service';
|
||||||
import { ConfigurationService } from './configuration.service';
|
import { ConfigurationService } from './configuration.service';
|
||||||
import { DirectoryService } from './directory.service';
|
import { IDirectoryService } from './directory.service';
|
||||||
|
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||||
import { LogService } from 'jslib/abstractions/log.service';
|
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||||
|
|
||||||
const NextLink = '@odata.nextLink';
|
const NextLink = '@odata.nextLink';
|
||||||
const DeltaLink = '@odata.deltaLink';
|
const DeltaLink = '@odata.deltaLink';
|
||||||
const ObjectType = '@odata.type';
|
const ObjectType = '@odata.type';
|
||||||
|
const UserSelectParams = '?$select=id,mail,userPrincipalName,displayName,accountEnabled';
|
||||||
|
|
||||||
enum UserSetType {
|
enum UserSetType {
|
||||||
IncludeUser,
|
IncludeUser,
|
||||||
@@ -28,7 +29,7 @@ enum UserSetType {
|
|||||||
ExcludeGroup,
|
ExcludeGroup,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AzureDirectoryService extends BaseDirectoryService implements DirectoryService {
|
export class AzureDirectoryService extends BaseDirectoryService implements IDirectoryService {
|
||||||
private client: graph.Client;
|
private client: graph.Client;
|
||||||
private dirConfig: AzureConfiguration;
|
private dirConfig: AzureConfiguration;
|
||||||
private syncConfig: SyncConfiguration;
|
private syncConfig: SyncConfiguration;
|
||||||
@@ -67,9 +68,9 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
|
|||||||
|
|
||||||
let groups: GroupEntry[];
|
let groups: GroupEntry[];
|
||||||
if (this.syncConfig.groups) {
|
if (this.syncConfig.groups) {
|
||||||
const setFilter = this.createCustomSet(this.syncConfig.groupFilter);
|
const setFilter = await this.createAadCustomSet(this.syncConfig.groupFilter);
|
||||||
groups = await this.getGroups(setFilter);
|
groups = await this.getGroups(setFilter);
|
||||||
users = this.filterUsersFromGroupsSet(users, groups, setFilter);
|
users = this.filterUsersFromGroupsSet(users, groups, setFilter, this.syncConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [groups, users];
|
return [groups, users];
|
||||||
@@ -78,7 +79,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
|
|||||||
private async getCurrentUsers(): Promise<UserEntry[]> {
|
private async getCurrentUsers(): Promise<UserEntry[]> {
|
||||||
const entryIds = new Set<string>();
|
const entryIds = new Set<string>();
|
||||||
const entries: UserEntry[] = [];
|
const entries: UserEntry[] = [];
|
||||||
const userReq = this.client.api('/users');
|
const userReq = this.client.api('/users' + UserSelectParams);
|
||||||
let res = await userReq.get();
|
let res = await userReq.get();
|
||||||
const setFilter = this.createCustomUserSet(this.syncConfig.userFilter);
|
const setFilter = this.createCustomUserSet(this.syncConfig.userFilter);
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -89,7 +90,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const entry = this.buildUser(user);
|
const entry = this.buildUser(user);
|
||||||
if (await this.filterOutUserResult(setFilter, entry)) {
|
if (await this.filterOutUserResult(setFilter, entry, true)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +131,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (res == null) {
|
if (res == null) {
|
||||||
const userReq = this.client.api('/users/delta');
|
const userReq = this.client.api('/users/delta' + UserSelectParams);
|
||||||
res = await userReq.get();
|
res = await userReq.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +147,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
|
|||||||
if (!entry.disabled && !entry.deleted) {
|
if (!entry.disabled && !entry.deleted) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (await this.filterOutUserResult(setFilter, entry)) {
|
if (await this.filterOutUserResult(setFilter, entry, false)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,6 +170,55 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
|
|||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async createAadCustomSet(filter: string): Promise<[boolean, Set<string>]> {
|
||||||
|
if (filter == null || filter === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainParts = filter.split('|');
|
||||||
|
if (mainParts.length < 1 || mainParts[0] == null || mainParts[0].trim() === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = mainParts[0].split(':');
|
||||||
|
if (parts.length !== 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyword = parts[0].trim().toLowerCase();
|
||||||
|
let exclude = true;
|
||||||
|
if (keyword === 'include') {
|
||||||
|
exclude = false;
|
||||||
|
} else if (keyword === 'exclude') {
|
||||||
|
exclude = true;
|
||||||
|
} else if (keyword === 'excludeadministrativeunit') {
|
||||||
|
exclude = true;
|
||||||
|
} else if (keyword === 'includeadministrativeunit') {
|
||||||
|
exclude = false;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const set = new Set<string>();
|
||||||
|
const pieces = parts[1].split(',');
|
||||||
|
if (keyword === 'excludeadministrativeunit' || keyword === 'includeadministrativeunit') {
|
||||||
|
for (const p of pieces) {
|
||||||
|
const auMembers = await this.client
|
||||||
|
.api(`https://graph.microsoft.com/v1.0/directory/administrativeUnits/${p}/members`).get();
|
||||||
|
for (const auMember of auMembers.value) {
|
||||||
|
if (auMember['@odata.type'] === '#microsoft.graph.group') {
|
||||||
|
set.add(auMember.displayName.toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const p of pieces) {
|
||||||
|
set.add(p.trim().toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [exclude, set];
|
||||||
|
}
|
||||||
|
|
||||||
private createCustomUserSet(filter: string): [UserSetType, Set<string>] {
|
private createCustomUserSet(filter: string): [UserSetType, Set<string>] {
|
||||||
if (filter == null || filter === '') {
|
if (filter == null || filter === '') {
|
||||||
return null;
|
return null;
|
||||||
@@ -207,7 +257,8 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
|
|||||||
return [userSetType, set];
|
return [userSetType, set];
|
||||||
}
|
}
|
||||||
|
|
||||||
private async filterOutUserResult(setFilter: [UserSetType, Set<string>], user: UserEntry): Promise<boolean> {
|
private async filterOutUserResult(setFilter: [UserSetType, Set<string>], user: UserEntry,
|
||||||
|
checkGroupsFilter: boolean): Promise<boolean> {
|
||||||
if (setFilter == null) {
|
if (setFilter == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -223,20 +274,22 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
|
|||||||
return this.filterOutResult([userSetTypeExclude, setFilter[1]], user.email);
|
return this.filterOutResult([userSetTypeExclude, setFilter[1]], user.email);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// We need to *not* call the /checkMemberGroups method for deleted users, it will always fail
|
||||||
const memberGroups = await this.client.api(`/users/${user.externalId}/checkMemberGroups`).post({
|
if (!checkGroupsFilter) {
|
||||||
groupIds: Array.from(setFilter[1]),
|
return false;
|
||||||
});
|
}
|
||||||
if (memberGroups.value.length > 0 && setFilter[0] === UserSetType.IncludeGroup) {
|
const memberGroups = await this.client.api(`/users/${user.externalId}/checkMemberGroups`).post({
|
||||||
return false;
|
groupIds: Array.from(setFilter[1]),
|
||||||
} else if (memberGroups.value.length > 0 && setFilter[0] === UserSetType.ExcludeGroup) {
|
});
|
||||||
return true;
|
if (memberGroups.value.length > 0 && setFilter[0] === UserSetType.IncludeGroup) {
|
||||||
} else if (memberGroups.value.length === 0 && setFilter[0] === UserSetType.IncludeGroup) {
|
return false;
|
||||||
return true;
|
} else if (memberGroups.value.length > 0 && setFilter[0] === UserSetType.ExcludeGroup) {
|
||||||
} else if (memberGroups.value.length === 0 && setFilter[0] === UserSetType.ExcludeGroup) {
|
return true;
|
||||||
return false;
|
} else if (memberGroups.value.length === 0 && setFilter[0] === UserSetType.IncludeGroup) {
|
||||||
}
|
return true;
|
||||||
} catch { }
|
} else if (memberGroups.value.length === 0 && setFilter[0] === UserSetType.ExcludeGroup) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -305,16 +358,24 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
|
|||||||
entry.name = group.displayName;
|
entry.name = group.displayName;
|
||||||
|
|
||||||
const memReq = this.client.api('/groups/' + group.id + '/members');
|
const memReq = this.client.api('/groups/' + group.id + '/members');
|
||||||
const memRes = await memReq.get();
|
let memRes = await memReq.get();
|
||||||
const members: any = memRes.value;
|
while (true) {
|
||||||
if (members != null) {
|
const members: any = memRes.value;
|
||||||
for (const member of members) {
|
if (members != null) {
|
||||||
if (member[ObjectType] === '#microsoft.graph.group') {
|
for (const member of members) {
|
||||||
entry.groupMemberReferenceIds.add((member as graphType.Group).id);
|
if (member[ObjectType] === '#microsoft.graph.group') {
|
||||||
} else if (member[ObjectType] === '#microsoft.graph.user') {
|
entry.groupMemberReferenceIds.add((member as graphType.Group).id);
|
||||||
entry.userMemberExternalIds.add((member as graphType.User).id);
|
} else if (member[ObjectType] === '#microsoft.graph.user') {
|
||||||
|
entry.userMemberExternalIds.add((member as graphType.User).id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (memRes[NextLink] == null) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
const nextMemReq = this.client.api(memRes[NextLink]);
|
||||||
|
memRes = await nextMemReq.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
@@ -322,7 +383,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
|
|||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
this.client = graph.Client.init({
|
this.client = graph.Client.init({
|
||||||
authProvider: (done) => {
|
authProvider: done => {
|
||||||
if (this.dirConfig.applicationId == null || this.dirConfig.key == null ||
|
if (this.dirConfig.applicationId == null || this.dirConfig.key == null ||
|
||||||
this.dirConfig.tenant == null) {
|
this.dirConfig.tenant == null) {
|
||||||
done(this.i18nService.t('dirConfigIncomplete'), null);
|
done(this.i18nService.t('dirConfigIncomplete'), null);
|
||||||
@@ -352,7 +413,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
|
|||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'Content-Length': Buffer.byteLength(data),
|
'Content-Length': Buffer.byteLength(data),
|
||||||
},
|
},
|
||||||
}, (res) => {
|
}, res => {
|
||||||
res.setEncoding('utf8');
|
res.setEncoding('utf8');
|
||||||
res.on('data', (chunk: string) => {
|
res.on('data', (chunk: string) => {
|
||||||
const d = JSON.parse(chunk);
|
const d = JSON.parse(chunk);
|
||||||
@@ -365,7 +426,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
|
|||||||
done('Unknown error (' + res.statusCode + ').', null);
|
done('Unknown error (' + res.statusCode + ').', null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}).on('error', (err) => {
|
}).on('error', err => {
|
||||||
done(err, null);
|
done(err, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { SyncConfiguration } from '../models/syncConfiguration';
|
||||||
|
|
||||||
import { GroupEntry } from '../models/groupEntry';
|
import { GroupEntry } from '../models/groupEntry';
|
||||||
import { UserEntry } from '../models/userEntry';
|
import { UserEntry } from '../models/userEntry';
|
||||||
|
|
||||||
@@ -66,21 +68,24 @@ export abstract class BaseDirectoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected filterUsersFromGroupsSet(users: UserEntry[], groups: GroupEntry[],
|
protected filterUsersFromGroupsSet(users: UserEntry[], groups: GroupEntry[],
|
||||||
setFilter: [boolean, Set<string>]): UserEntry[] {
|
setFilter: [boolean, Set<string>], syncConfig: SyncConfiguration): UserEntry[] {
|
||||||
if (setFilter == null || users == null) {
|
if (setFilter == null || users == null) {
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|
||||||
return users.filter((u) => {
|
return users.filter(u => {
|
||||||
if (u.disabled || u.deleted) {
|
if (u.deleted) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (u.disabled && syncConfig.removeDisabled) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return groups.filter((g) => g.userMemberExternalIds.has(u.externalId)).length > 0;
|
return groups.filter(g => g.userMemberExternalIds.has(u.externalId)).length > 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected forceGroup(force: boolean, users: UserEntry[]): boolean {
|
protected forceGroup(force: boolean, users: UserEntry[]): boolean {
|
||||||
return force || (users != null && users.filter((u) => !u.deleted && !u.disabled).length > 0);
|
return force || (users != null && users.filter(u => !u.deleted && !u.disabled).length > 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { DirectoryType } from '../enums/directoryType';
|
import { DirectoryType } from '../enums/directoryType';
|
||||||
|
|
||||||
import { StorageService } from 'jslib/abstractions/storage.service';
|
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
||||||
import { AzureConfiguration } from '../models/azureConfiguration';
|
import { AzureConfiguration } from '../models/azureConfiguration';
|
||||||
import { GSuiteConfiguration } from '../models/gsuiteConfiguration';
|
import { GSuiteConfiguration } from '../models/gsuiteConfiguration';
|
||||||
import { LdapConfiguration } from '../models/ldapConfiguration';
|
import { LdapConfiguration } from '../models/ldapConfiguration';
|
||||||
import { OktaConfiguration } from '../models/oktaConfiguration';
|
import { OktaConfiguration } from '../models/oktaConfiguration';
|
||||||
|
import { OneLoginConfiguration } from '../models/oneLoginConfiguration';
|
||||||
import { SyncConfiguration } from '../models/syncConfiguration';
|
import { SyncConfiguration } from '../models/syncConfiguration';
|
||||||
|
|
||||||
const StoredSecurely = '[STORED SECURELY]';
|
const StoredSecurely = '[STORED SECURELY]';
|
||||||
@@ -13,6 +14,7 @@ const Keys = {
|
|||||||
gsuite: 'gsuitePrivateKey',
|
gsuite: 'gsuitePrivateKey',
|
||||||
azure: 'azureKey',
|
azure: 'azureKey',
|
||||||
okta: 'oktaToken',
|
okta: 'oktaToken',
|
||||||
|
oneLogin: 'oneLoginClientSecret',
|
||||||
directoryConfigPrefix: 'directoryConfig_',
|
directoryConfigPrefix: 'directoryConfig_',
|
||||||
sync: 'syncConfig',
|
sync: 'syncConfig',
|
||||||
directoryType: 'directoryType',
|
directoryType: 'directoryType',
|
||||||
@@ -25,7 +27,8 @@ const Keys = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class ConfigurationService {
|
export class ConfigurationService {
|
||||||
constructor(private storageService: StorageService, private secureStorageService: StorageService) { }
|
constructor(private storageService: StorageService, private secureStorageService: StorageService,
|
||||||
|
private useSecureStorageForSecrets = true) { }
|
||||||
|
|
||||||
async getDirectory<T>(type: DirectoryType): Promise<T> {
|
async getDirectory<T>(type: DirectoryType): Promise<T> {
|
||||||
const config = await this.storageService.get<T>(Keys.directoryConfigPrefix + type);
|
const config = await this.storageService.get<T>(Keys.directoryConfigPrefix + type);
|
||||||
@@ -33,61 +36,77 @@ export class ConfigurationService {
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (type) {
|
if (this.useSecureStorageForSecrets) {
|
||||||
case DirectoryType.Ldap:
|
switch (type) {
|
||||||
(config as any).password = await this.secureStorageService.get<string>(Keys.ldap);
|
case DirectoryType.Ldap:
|
||||||
break;
|
(config as any).password = await this.secureStorageService.get<string>(Keys.ldap);
|
||||||
case DirectoryType.AzureActiveDirectory:
|
break;
|
||||||
(config as any).key = await this.secureStorageService.get<string>(Keys.azure);
|
case DirectoryType.AzureActiveDirectory:
|
||||||
break;
|
(config as any).key = await this.secureStorageService.get<string>(Keys.azure);
|
||||||
case DirectoryType.Okta:
|
break;
|
||||||
(config as any).token = await this.secureStorageService.get<string>(Keys.okta);
|
case DirectoryType.Okta:
|
||||||
break;
|
(config as any).token = await this.secureStorageService.get<string>(Keys.okta);
|
||||||
case DirectoryType.GSuite:
|
break;
|
||||||
(config as any).privateKey = await this.secureStorageService.get<string>(Keys.gsuite);
|
case DirectoryType.GSuite:
|
||||||
break;
|
(config as any).privateKey = await this.secureStorageService.get<string>(Keys.gsuite);
|
||||||
|
break;
|
||||||
|
case DirectoryType.OneLogin:
|
||||||
|
(config as any).clientSecret = await this.secureStorageService.get<string>(Keys.oneLogin);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveDirectory(type: DirectoryType,
|
async saveDirectory(type: DirectoryType,
|
||||||
config: LdapConfiguration | GSuiteConfiguration | AzureConfiguration | OktaConfiguration): Promise<any> {
|
config: LdapConfiguration | GSuiteConfiguration | AzureConfiguration | OktaConfiguration |
|
||||||
|
OneLoginConfiguration): Promise<any> {
|
||||||
const savedConfig: any = Object.assign({}, config);
|
const savedConfig: any = Object.assign({}, config);
|
||||||
switch (type) {
|
if (this.useSecureStorageForSecrets) {
|
||||||
case DirectoryType.Ldap:
|
switch (type) {
|
||||||
if (savedConfig.password == null) {
|
case DirectoryType.Ldap:
|
||||||
await this.secureStorageService.remove(Keys.ldap);
|
if (savedConfig.password == null) {
|
||||||
} else {
|
await this.secureStorageService.remove(Keys.ldap);
|
||||||
await this.secureStorageService.save(Keys.ldap, savedConfig.password);
|
} else {
|
||||||
savedConfig.password = StoredSecurely;
|
await this.secureStorageService.save(Keys.ldap, savedConfig.password);
|
||||||
}
|
savedConfig.password = StoredSecurely;
|
||||||
break;
|
}
|
||||||
case DirectoryType.AzureActiveDirectory:
|
break;
|
||||||
if (savedConfig.key == null) {
|
case DirectoryType.AzureActiveDirectory:
|
||||||
await this.secureStorageService.remove(Keys.azure);
|
if (savedConfig.key == null) {
|
||||||
} else {
|
await this.secureStorageService.remove(Keys.azure);
|
||||||
await this.secureStorageService.save(Keys.azure, savedConfig.key);
|
} else {
|
||||||
savedConfig.key = StoredSecurely;
|
await this.secureStorageService.save(Keys.azure, savedConfig.key);
|
||||||
}
|
savedConfig.key = StoredSecurely;
|
||||||
break;
|
}
|
||||||
case DirectoryType.Okta:
|
break;
|
||||||
if (savedConfig.token == null) {
|
case DirectoryType.Okta:
|
||||||
await this.secureStorageService.remove(Keys.okta);
|
if (savedConfig.token == null) {
|
||||||
} else {
|
await this.secureStorageService.remove(Keys.okta);
|
||||||
await this.secureStorageService.save(Keys.okta, savedConfig.token);
|
} else {
|
||||||
savedConfig.token = StoredSecurely;
|
await this.secureStorageService.save(Keys.okta, savedConfig.token);
|
||||||
}
|
savedConfig.token = StoredSecurely;
|
||||||
break;
|
}
|
||||||
case DirectoryType.GSuite:
|
break;
|
||||||
if (savedConfig.privateKey == null) {
|
case DirectoryType.GSuite:
|
||||||
await this.secureStorageService.remove(Keys.gsuite);
|
if (savedConfig.privateKey == null) {
|
||||||
} else {
|
await this.secureStorageService.remove(Keys.gsuite);
|
||||||
(config as GSuiteConfiguration).privateKey = savedConfig.privateKey =
|
} else {
|
||||||
savedConfig.privateKey.replace(/\\n/g, '\n');
|
(config as GSuiteConfiguration).privateKey = savedConfig.privateKey =
|
||||||
await this.secureStorageService.save(Keys.gsuite, savedConfig.privateKey);
|
savedConfig.privateKey.replace(/\\n/g, '\n');
|
||||||
savedConfig.privateKey = StoredSecurely;
|
await this.secureStorageService.save(Keys.gsuite, savedConfig.privateKey);
|
||||||
}
|
savedConfig.privateKey = StoredSecurely;
|
||||||
break;
|
}
|
||||||
|
break;
|
||||||
|
case DirectoryType.OneLogin:
|
||||||
|
if (savedConfig.clientSecret == null) {
|
||||||
|
await this.secureStorageService.remove(Keys.oneLogin);
|
||||||
|
} else {
|
||||||
|
await this.secureStorageService.save(Keys.oneLogin, savedConfig.clientSecret);
|
||||||
|
savedConfig.clientSecret = StoredSecurely;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await this.storageService.save(Keys.directoryConfigPrefix + type, savedConfig);
|
await this.storageService.save(Keys.directoryConfigPrefix + type, savedConfig);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { GroupEntry } from '../models/groupEntry';
|
import { GroupEntry } from '../models/groupEntry';
|
||||||
import { UserEntry } from '../models/userEntry';
|
import { UserEntry } from '../models/userEntry';
|
||||||
|
|
||||||
export interface DirectoryService {
|
export interface IDirectoryService {
|
||||||
getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]>;
|
getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ import { UserEntry } from '../models/userEntry';
|
|||||||
|
|
||||||
import { BaseDirectoryService } from './baseDirectory.service';
|
import { BaseDirectoryService } from './baseDirectory.service';
|
||||||
import { ConfigurationService } from './configuration.service';
|
import { ConfigurationService } from './configuration.service';
|
||||||
import { DirectoryService } from './directory.service';
|
import { IDirectoryService } from './directory.service';
|
||||||
|
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||||
import { LogService } from 'jslib/abstractions/log.service';
|
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||||
|
|
||||||
export class GSuiteDirectoryService extends BaseDirectoryService implements DirectoryService {
|
export class GSuiteDirectoryService extends BaseDirectoryService implements IDirectoryService {
|
||||||
private client: JWT;
|
private client: JWT;
|
||||||
private service: admin_directory_v1.Admin;
|
private service: admin_directory_v1.Admin;
|
||||||
private authParams: any;
|
private authParams: any;
|
||||||
@@ -49,7 +49,7 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements Dire
|
|||||||
|
|
||||||
await this.auth();
|
await this.auth();
|
||||||
|
|
||||||
let users: UserEntry[];
|
let users: UserEntry[] = [];
|
||||||
if (this.syncConfig.users) {
|
if (this.syncConfig.users) {
|
||||||
users = await this.getUsers();
|
users = await this.getUsers();
|
||||||
}
|
}
|
||||||
@@ -57,8 +57,8 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements Dire
|
|||||||
let groups: GroupEntry[];
|
let groups: GroupEntry[];
|
||||||
if (this.syncConfig.groups) {
|
if (this.syncConfig.groups) {
|
||||||
const setFilter = this.createCustomSet(this.syncConfig.groupFilter);
|
const setFilter = this.createCustomSet(this.syncConfig.groupFilter);
|
||||||
groups = await this.getGroups(setFilter);
|
groups = await this.getGroups(setFilter, users);
|
||||||
users = this.filterUsersFromGroupsSet(users, groups, setFilter);
|
users = this.filterUsersFromGroupsSet(users, groups, setFilter, this.syncConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [groups, users];
|
return [groups, users];
|
||||||
@@ -140,7 +140,7 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements Dire
|
|||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getGroups(setFilter: [boolean, Set<string>]): Promise<GroupEntry[]> {
|
private async getGroups(setFilter: [boolean, Set<string>], users: UserEntry[]): Promise<GroupEntry[]> {
|
||||||
const entries: GroupEntry[] = [];
|
const entries: GroupEntry[] = [];
|
||||||
let nextPageToken: string = null;
|
let nextPageToken: string = null;
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements Dire
|
|||||||
if (res.data.groups != null) {
|
if (res.data.groups != null) {
|
||||||
for (const group of res.data.groups) {
|
for (const group of res.data.groups) {
|
||||||
if (!this.filterOutResult(setFilter, group.name)) {
|
if (!this.filterOutResult(setFilter, group.name)) {
|
||||||
const entry = await this.buildGroup(group);
|
const entry = await this.buildGroup(group, users);
|
||||||
entries.push(entry);
|
entries.push(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,7 +170,7 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements Dire
|
|||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async buildGroup(group: admin_directory_v1.Schema$Group) {
|
private async buildGroup(group: admin_directory_v1.Schema$Group, users: UserEntry[]) {
|
||||||
let nextPageToken: string = null;
|
let nextPageToken: string = null;
|
||||||
|
|
||||||
const entry = new GroupEntry();
|
const entry = new GroupEntry();
|
||||||
@@ -192,18 +192,18 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements Dire
|
|||||||
if (member.type == null) {
|
if (member.type == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (member.role == null || member.role.toLowerCase() !== 'member') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (member.status == null || member.status.toLowerCase() !== 'active') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const type = member.type.toLowerCase();
|
const type = member.type.toLowerCase();
|
||||||
if (type === 'user') {
|
if (type === 'user') {
|
||||||
|
if (member.status == null || member.status.toLowerCase() !== 'active') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
entry.userMemberExternalIds.add(member.id);
|
entry.userMemberExternalIds.add(member.id);
|
||||||
} else if (type === 'group') {
|
} else if (type === 'group') {
|
||||||
entry.groupMemberReferenceIds.add(member.id);
|
entry.groupMemberReferenceIds.add(member.id);
|
||||||
|
} else if (type === 'customer') {
|
||||||
|
for (const user of users) {
|
||||||
|
entry.userMemberExternalIds.add(user.externalId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { I18nService as BaseI18nService } from 'jslib/services/i18n.service';
|
import { I18nService as BaseI18nService } from 'jslib-common/services/i18n.service';
|
||||||
|
|
||||||
export class I18nService extends BaseI18nService {
|
export class I18nService extends BaseI18nService {
|
||||||
constructor(systemLanguage: string, localesDirectory: string) {
|
constructor(systemLanguage: string, localesDirectory: string) {
|
||||||
|
|||||||
@@ -4,17 +4,21 @@ import {
|
|||||||
setPassword,
|
setPassword,
|
||||||
} from 'keytar';
|
} from 'keytar';
|
||||||
|
|
||||||
import { StorageService } from 'jslib/abstractions/storage.service';
|
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
||||||
|
|
||||||
export class KeytarSecureStorageService implements StorageService {
|
export class KeytarSecureStorageService implements StorageService {
|
||||||
constructor(private serviceName: string) { }
|
constructor(private serviceName: string) { }
|
||||||
|
|
||||||
get<T>(key: string): Promise<T> {
|
get<T>(key: string): Promise<T> {
|
||||||
return getPassword(this.serviceName, key).then((val) => {
|
return getPassword(this.serviceName, key).then(val => {
|
||||||
return JSON.parse(val) as T;
|
return JSON.parse(val) as T;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async has(key: string): Promise<boolean> {
|
||||||
|
return (await this.get(key)) != null;
|
||||||
|
}
|
||||||
|
|
||||||
save(key: string, obj: any): Promise<any> {
|
save(key: string, obj: any): Promise<any> {
|
||||||
return setPassword(this.serviceName, key, JSON.stringify(obj));
|
return setPassword(this.serviceName, key, JSON.stringify(obj));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as ldap from 'ldapjs';
|
import * as ldap from 'ldapjs';
|
||||||
|
|
||||||
|
import { checkServerIdentity, PeerCertificate } from 'tls';
|
||||||
|
|
||||||
import { DirectoryType } from '../enums/directoryType';
|
import { DirectoryType } from '../enums/directoryType';
|
||||||
|
|
||||||
import { GroupEntry } from '../models/groupEntry';
|
import { GroupEntry } from '../models/groupEntry';
|
||||||
@@ -9,16 +11,16 @@ import { SyncConfiguration } from '../models/syncConfiguration';
|
|||||||
import { UserEntry } from '../models/userEntry';
|
import { UserEntry } from '../models/userEntry';
|
||||||
|
|
||||||
import { ConfigurationService } from './configuration.service';
|
import { ConfigurationService } from './configuration.service';
|
||||||
import { DirectoryService } from './directory.service';
|
import { IDirectoryService } from './directory.service';
|
||||||
|
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||||
import { LogService } from 'jslib/abstractions/log.service';
|
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||||
|
|
||||||
import { Utils } from 'jslib/misc/utils';
|
import { Utils } from 'jslib-common/misc/utils';
|
||||||
|
|
||||||
const UserControlAccountDisabled = 2;
|
const UserControlAccountDisabled = 2;
|
||||||
|
|
||||||
export class LdapDirectoryService implements DirectoryService {
|
export class LdapDirectoryService implements IDirectoryService {
|
||||||
private client: ldap.Client;
|
private client: ldap.Client;
|
||||||
private dirConfig: LdapConfiguration;
|
private dirConfig: LdapConfiguration;
|
||||||
private syncConfig: SyncConfiguration;
|
private syncConfig: SyncConfiguration;
|
||||||
@@ -53,7 +55,7 @@ export class LdapDirectoryService implements DirectoryService {
|
|||||||
if (this.syncConfig.groups) {
|
if (this.syncConfig.groups) {
|
||||||
let groupForce = force;
|
let groupForce = force;
|
||||||
if (!groupForce && users != null) {
|
if (!groupForce && users != null) {
|
||||||
const activeUsers = users.filter((u) => !u.deleted && !u.disabled);
|
const activeUsers = users.filter(u => !u.deleted && !u.disabled);
|
||||||
groupForce = activeUsers.length > 0;
|
groupForce = activeUsers.length > 0;
|
||||||
}
|
}
|
||||||
groups = await this.getGroups(groupForce);
|
groups = await this.getGroups(groupForce);
|
||||||
@@ -231,6 +233,9 @@ export class LdapDirectoryService implements DirectoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private makeSearchPath(pathPrefix: string) {
|
private makeSearchPath(pathPrefix: string) {
|
||||||
|
if (this.dirConfig.rootPath.toLowerCase().indexOf('dc=') === -1) {
|
||||||
|
return pathPrefix;
|
||||||
|
}
|
||||||
if (this.dirConfig.rootPath != null && this.dirConfig.rootPath.trim() !== '') {
|
if (this.dirConfig.rootPath != null && this.dirConfig.rootPath.trim() !== '') {
|
||||||
const trimmedRootPath = this.dirConfig.rootPath.trim().toLowerCase();
|
const trimmedRootPath = this.dirConfig.rootPath.trim().toLowerCase();
|
||||||
let path = trimmedRootPath.substr(trimmedRootPath.indexOf('dc='));
|
let path = trimmedRootPath.substr(trimmedRootPath.indexOf('dc='));
|
||||||
@@ -290,7 +295,7 @@ export class LdapDirectoryService implements DirectoryService {
|
|||||||
const options: ldap.SearchOptions = {
|
const options: ldap.SearchOptions = {
|
||||||
filter: filter,
|
filter: filter,
|
||||||
scope: 'sub',
|
scope: 'sub',
|
||||||
paged: true,
|
paged: this.dirConfig.pagedSearch,
|
||||||
};
|
};
|
||||||
const entries: T[] = [];
|
const entries: T[] = [];
|
||||||
return new Promise<T[]>((resolve, reject) => {
|
return new Promise<T[]>((resolve, reject) => {
|
||||||
@@ -300,18 +305,18 @@ export class LdapDirectoryService implements DirectoryService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.on('error', (resErr) => {
|
res.on('error', resErr => {
|
||||||
reject(resErr);
|
reject(resErr);
|
||||||
});
|
});
|
||||||
|
|
||||||
res.on('searchEntry', (entry) => {
|
res.on('searchEntry', entry => {
|
||||||
const e = processEntry(entry);
|
const e = processEntry(entry);
|
||||||
if (e != null) {
|
if (e != null) {
|
||||||
entries.push(e);
|
entries.push(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
res.on('end', (result) => {
|
res.on('end', result => {
|
||||||
resolve(entries);
|
resolve(entries);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -319,39 +324,47 @@ export class LdapDirectoryService implements DirectoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async bind(): Promise<any> {
|
private async bind(): Promise<any> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
if (this.dirConfig.hostname == null || this.dirConfig.port == null) {
|
if (this.dirConfig.hostname == null || this.dirConfig.port == null) {
|
||||||
reject(this.i18nService.t('dirConfigIncomplete'));
|
reject(this.i18nService.t('dirConfigIncomplete'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const protocol = 'ldap' + (this.dirConfig.ssl && !this.dirConfig.startTls ? 's' : '');
|
||||||
const url = 'ldap' + (this.dirConfig.ssl ? 's' : '') + '://' + this.dirConfig.hostname +
|
const url = protocol + '://' + this.dirConfig.hostname +
|
||||||
':' + this.dirConfig.port;
|
':' + this.dirConfig.port;
|
||||||
const options: ldap.ClientOptions = {
|
const options: ldap.ClientOptions = {
|
||||||
url: url.trim().toLowerCase(),
|
url: url.trim().toLowerCase(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const tlsOptions: any = {};
|
||||||
if (this.dirConfig.ssl) {
|
if (this.dirConfig.ssl) {
|
||||||
const tlsOptions: any = {};
|
if (this.dirConfig.sslAllowUnauthorized) {
|
||||||
if (this.dirConfig.sslAllowUnauthorized != null) {
|
|
||||||
tlsOptions.rejectUnauthorized = !this.dirConfig.sslAllowUnauthorized;
|
tlsOptions.rejectUnauthorized = !this.dirConfig.sslAllowUnauthorized;
|
||||||
}
|
}
|
||||||
if (this.dirConfig.sslCaPath != null && this.dirConfig.sslCaPath !== '' &&
|
if (!this.dirConfig.startTls) {
|
||||||
fs.existsSync(this.dirConfig.sslCaPath)) {
|
if (this.dirConfig.sslCaPath != null && this.dirConfig.sslCaPath !== '' &&
|
||||||
tlsOptions.ca = [fs.readFileSync(this.dirConfig.sslCaPath)];
|
fs.existsSync(this.dirConfig.sslCaPath)) {
|
||||||
}
|
tlsOptions.ca = [fs.readFileSync(this.dirConfig.sslCaPath)];
|
||||||
if (this.dirConfig.sslCertPath != null && this.dirConfig.sslCertPath !== '' &&
|
}
|
||||||
fs.existsSync(this.dirConfig.sslCertPath)) {
|
if (this.dirConfig.sslCertPath != null && this.dirConfig.sslCertPath !== '' &&
|
||||||
tlsOptions.cert = fs.readFileSync(this.dirConfig.sslCertPath);
|
fs.existsSync(this.dirConfig.sslCertPath)) {
|
||||||
}
|
tlsOptions.cert = fs.readFileSync(this.dirConfig.sslCertPath);
|
||||||
if (this.dirConfig.sslKeyPath != null && this.dirConfig.sslKeyPath !== '' &&
|
}
|
||||||
fs.existsSync(this.dirConfig.sslKeyPath)) {
|
if (this.dirConfig.sslKeyPath != null && this.dirConfig.sslKeyPath !== '' &&
|
||||||
tlsOptions.key = fs.readFileSync(this.dirConfig.sslKeyPath);
|
fs.existsSync(this.dirConfig.sslKeyPath)) {
|
||||||
}
|
tlsOptions.key = fs.readFileSync(this.dirConfig.sslKeyPath);
|
||||||
if (Object.keys(tlsOptions).length > 0) {
|
}
|
||||||
options.tlsOptions = tlsOptions;
|
} else {
|
||||||
|
if (this.dirConfig.tlsCaPath != null && this.dirConfig.tlsCaPath !== '' &&
|
||||||
|
fs.existsSync(this.dirConfig.tlsCaPath)) {
|
||||||
|
tlsOptions.ca = [fs.readFileSync(this.dirConfig.tlsCaPath)];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tlsOptions.checkServerIdentity = this.checkServerIdentityAltNames;
|
||||||
|
options.tlsOptions = tlsOptions;
|
||||||
|
|
||||||
this.client = ldap.createClient(options);
|
this.client = ldap.createClient(options);
|
||||||
|
|
||||||
const user = this.dirConfig.username == null || this.dirConfig.username.trim() === '' ? null :
|
const user = this.dirConfig.username == null || this.dirConfig.username.trim() === '' ? null :
|
||||||
@@ -364,19 +377,35 @@ export class LdapDirectoryService implements DirectoryService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.client.bind(user, pass, (err) => {
|
if (this.dirConfig.startTls && this.dirConfig.ssl) {
|
||||||
if (err != null) {
|
this.client.starttls(options.tlsOptions, undefined, (err, res) => {
|
||||||
reject(err.message);
|
if (err != null) {
|
||||||
} else {
|
reject(err.message);
|
||||||
resolve();
|
} else {
|
||||||
}
|
this.client.bind(user, pass, err2 => {
|
||||||
});
|
if (err2 != null) {
|
||||||
|
reject(err2.message);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.client.bind(user, pass, err => {
|
||||||
|
if (err != null) {
|
||||||
|
reject(err.message);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async unbind(): Promise<any> {
|
private async unbind(): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.client.unbind((err) => {
|
this.client.unbind(err => {
|
||||||
if (err != null) {
|
if (err != null) {
|
||||||
reject(err);
|
reject(err);
|
||||||
} else {
|
} else {
|
||||||
@@ -397,4 +426,23 @@ export class LdapDirectoryService implements DirectoryService {
|
|||||||
'-' + Utils.fromBufferToHex(p4) + '-' + Utils.fromBufferToHex(p5);
|
'-' + Utils.fromBufferToHex(p4) + '-' + Utils.fromBufferToHex(p5);
|
||||||
return guid.toLowerCase();
|
return guid.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private checkServerIdentityAltNames(host: string, cert: PeerCertificate) {
|
||||||
|
// Fixes the cert representation when subject is empty and altNames are present
|
||||||
|
// Required for node versions < 12.14.1 (which could be used for bwdc cli)
|
||||||
|
// Adapted from: https://github.com/auth0/ad-ldap-connector/commit/1f4dd2be6ed93dda591dd31ed5483a9b452a8d2a
|
||||||
|
// See https://github.com/nodejs/node/issues/11771 for details
|
||||||
|
if (cert && cert.subject == null && /(IP|DNS|URL)/.test(cert.subjectaltname)) {
|
||||||
|
cert.subject = {
|
||||||
|
C: null,
|
||||||
|
ST: null,
|
||||||
|
L: null,
|
||||||
|
O: null,
|
||||||
|
OU: null,
|
||||||
|
CN: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkServerIdentity(host, cert);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
28
src/services/lowdbStorage.service.ts
Normal file
28
src/services/lowdbStorage.service.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import * as lock from 'proper-lockfile';
|
||||||
|
|
||||||
|
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||||
|
|
||||||
|
import { LowdbStorageService as LowdbStorageServiceBase } from 'jslib-node/services/lowdbStorage.service';
|
||||||
|
|
||||||
|
import { Utils } from 'jslib-common/misc/utils';
|
||||||
|
|
||||||
|
export class LowdbStorageService extends LowdbStorageServiceBase {
|
||||||
|
constructor(logService: LogService, defaults?: any, dir?: string, allowCache = false, private requireLock = false) {
|
||||||
|
super(logService, defaults, dir, allowCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async lockDbFile<T>(action: () => T): Promise<T> {
|
||||||
|
if (this.requireLock && !Utils.isNullOrWhitespace(this.dataFilePath)) {
|
||||||
|
this.logService.info('acquiring db file lock');
|
||||||
|
return await lock.lock(this.dataFilePath, { retries: 3 }).then(release => {
|
||||||
|
try {
|
||||||
|
return action();
|
||||||
|
} finally {
|
||||||
|
release();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return action();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/services/nodeApi.service.ts
Normal file
17
src/services/nodeApi.service.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
||||||
|
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||||
|
import { TokenService } from 'jslib-common/abstractions/token.service';
|
||||||
|
|
||||||
|
import { NodeApiService as NodeApiServiceBase } from 'jslib-node/services/nodeApi.service';
|
||||||
|
|
||||||
|
export class NodeApiService extends NodeApiServiceBase {
|
||||||
|
constructor(tokenService: TokenService, platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
|
||||||
|
private refreshTokenCallback: () => Promise<void>, logoutCallback: (expired: boolean) => Promise<void>,
|
||||||
|
customUserAgent: string = null) {
|
||||||
|
super(tokenService, platformUtilsService, environmentService, logoutCallback, customUserAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
doRefreshToken(): Promise<void> {
|
||||||
|
return this.refreshTokenCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,18 +7,19 @@ import { UserEntry } from '../models/userEntry';
|
|||||||
|
|
||||||
import { BaseDirectoryService } from './baseDirectory.service';
|
import { BaseDirectoryService } from './baseDirectory.service';
|
||||||
import { ConfigurationService } from './configuration.service';
|
import { ConfigurationService } from './configuration.service';
|
||||||
import { DirectoryService } from './directory.service';
|
import { IDirectoryService } from './directory.service';
|
||||||
|
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||||
import { LogService } from 'jslib/abstractions/log.service';
|
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||||
|
|
||||||
// tslint:disable-next-line
|
import * as https from 'https';
|
||||||
const okta = require('@okta/okta-sdk-nodejs');
|
|
||||||
|
|
||||||
export class OktaDirectoryService extends BaseDirectoryService implements DirectoryService {
|
const DelayBetweenBuildGroupCallsInMilliseconds = 500;
|
||||||
|
|
||||||
|
export class OktaDirectoryService extends BaseDirectoryService implements IDirectoryService {
|
||||||
private dirConfig: OktaConfiguration;
|
private dirConfig: OktaConfiguration;
|
||||||
private syncConfig: SyncConfiguration;
|
private syncConfig: SyncConfiguration;
|
||||||
private client: any;
|
private lastBuildGroupCall: number;
|
||||||
|
|
||||||
constructor(private configurationService: ConfigurationService, private logService: LogService,
|
constructor(private configurationService: ConfigurationService, private logService: LogService,
|
||||||
private i18nService: I18nService) {
|
private i18nService: I18nService) {
|
||||||
@@ -45,11 +46,6 @@ export class OktaDirectoryService extends BaseDirectoryService implements Direct
|
|||||||
throw new Error(this.i18nService.t('dirConfigIncomplete'));
|
throw new Error(this.i18nService.t('dirConfigIncomplete'));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.client = new okta.Client({
|
|
||||||
orgUrl: this.dirConfig.orgUrl,
|
|
||||||
token: this.dirConfig.token,
|
|
||||||
});
|
|
||||||
|
|
||||||
let users: UserEntry[];
|
let users: UserEntry[];
|
||||||
if (this.syncConfig.users) {
|
if (this.syncConfig.users) {
|
||||||
users = await this.getUsers(force);
|
users = await this.getUsers(force);
|
||||||
@@ -59,7 +55,7 @@ export class OktaDirectoryService extends BaseDirectoryService implements Direct
|
|||||||
if (this.syncConfig.groups) {
|
if (this.syncConfig.groups) {
|
||||||
const setFilter = this.createCustomSet(this.syncConfig.groupFilter);
|
const setFilter = this.createCustomSet(this.syncConfig.groupFilter);
|
||||||
groups = await this.getGroups(this.forceGroup(force, users), setFilter);
|
groups = await this.getGroups(this.forceGroup(force, users), setFilter);
|
||||||
users = this.filterUsersFromGroupsSet(users, groups, setFilter);
|
users = this.filterUsersFromGroupsSet(users, groups, setFilter, this.syncConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [groups, users];
|
return [groups, users];
|
||||||
@@ -72,12 +68,15 @@ export class OktaDirectoryService extends BaseDirectoryService implements Direct
|
|||||||
const setFilter = this.createCustomSet(this.syncConfig.userFilter);
|
const setFilter = this.createCustomSet(this.syncConfig.userFilter);
|
||||||
|
|
||||||
this.logService.info('Querying users.');
|
this.logService.info('Querying users.');
|
||||||
const usersPromise = this.client.listUsers({ filter: oktaFilter }).each((user: any) => {
|
const usersPromise = this.apiGetMany('users?filter=' + this.encodeUrlParameter(oktaFilter))
|
||||||
const entry = this.buildUser(user);
|
.then((users: any[]) => {
|
||||||
if (entry != null && !this.filterOutResult(setFilter, entry.email)) {
|
for (const user of users) {
|
||||||
entries.push(entry);
|
const entry = this.buildUser(user);
|
||||||
}
|
if (entry != null && !this.filterOutResult(setFilter, entry.email)) {
|
||||||
});
|
entries.push(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Deactivated users have to be queried for separately, only when no filter is provided in the first query
|
// Deactivated users have to be queried for separately, only when no filter is provided in the first query
|
||||||
let deactUsersPromise: any;
|
let deactUsersPromise: any;
|
||||||
@@ -86,12 +85,15 @@ export class OktaDirectoryService extends BaseDirectoryService implements Direct
|
|||||||
if (oktaFilter != null) {
|
if (oktaFilter != null) {
|
||||||
deactOktaFilter = '(' + oktaFilter + ') and ' + deactOktaFilter;
|
deactOktaFilter = '(' + oktaFilter + ') and ' + deactOktaFilter;
|
||||||
}
|
}
|
||||||
deactUsersPromise = this.client.listUsers({ filter: deactOktaFilter }).each((user: any) => {
|
deactUsersPromise = this.apiGetMany('users?filter=' + this.encodeUrlParameter(deactOktaFilter))
|
||||||
const entry = this.buildUser(user);
|
.then((users: any[]) => {
|
||||||
if (entry != null && !this.filterOutResult(setFilter, entry.email)) {
|
for (const user of users) {
|
||||||
entries.push(entry);
|
const entry = this.buildUser(user);
|
||||||
}
|
if (entry != null && !this.filterOutResult(setFilter, entry.email)) {
|
||||||
});
|
entries.push(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
deactUsersPromise = Promise.resolve();
|
deactUsersPromise = Promise.resolve();
|
||||||
}
|
}
|
||||||
@@ -116,10 +118,12 @@ export class OktaDirectoryService extends BaseDirectoryService implements Direct
|
|||||||
const oktaFilter = this.buildOktaFilter(this.syncConfig.groupFilter, force, lastSync);
|
const oktaFilter = this.buildOktaFilter(this.syncConfig.groupFilter, force, lastSync);
|
||||||
|
|
||||||
this.logService.info('Querying groups.');
|
this.logService.info('Querying groups.');
|
||||||
await this.client.listGroups({ filter: oktaFilter }).each(async (group: any) => {
|
await this.apiGetMany('groups?filter=' + this.encodeUrlParameter(oktaFilter)).then(async (groups: any[]) => {
|
||||||
const entry = await this.buildGroup(group);
|
for (const group of groups.filter(g => !this.filterOutResult(setFilter, g.profile.name))) {
|
||||||
if (entry != null && !this.filterOutResult(setFilter, entry.name)) {
|
const entry = await this.buildGroup(group);
|
||||||
entries.push(entry);
|
if (entry != null) {
|
||||||
|
entries.push(entry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return entries;
|
return entries;
|
||||||
@@ -131,8 +135,18 @@ export class OktaDirectoryService extends BaseDirectoryService implements Direct
|
|||||||
entry.referenceId = group.id;
|
entry.referenceId = group.id;
|
||||||
entry.name = group.profile.name;
|
entry.name = group.profile.name;
|
||||||
|
|
||||||
await this.client.listGroupUsers(group.id).each((user: any) => {
|
// throttle some to avoid rate limiting
|
||||||
entry.userMemberExternalIds.add(user.id);
|
const neededDelay = DelayBetweenBuildGroupCallsInMilliseconds - (Date.now() - this.lastBuildGroupCall);
|
||||||
|
if (neededDelay > 0) {
|
||||||
|
await new Promise(resolve =>
|
||||||
|
setTimeout(resolve, neededDelay));
|
||||||
|
}
|
||||||
|
this.lastBuildGroupCall = Date.now();
|
||||||
|
|
||||||
|
await this.apiGetMany('groups/' + group.id + '/users').then((users: any[]) => {
|
||||||
|
for (const user of users) {
|
||||||
|
entry.userMemberExternalIds.add(user.id);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
@@ -152,4 +166,88 @@ export class OktaDirectoryService extends BaseDirectoryService implements Direct
|
|||||||
|
|
||||||
return '(' + baseFilter + ') and ' + updatedFilter;
|
return '(' + baseFilter + ') and ' + updatedFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private encodeUrlParameter(filter: string): string {
|
||||||
|
return filter == null ? '' : encodeURIComponent(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async apiGetCall(url: string): Promise<[any, Map<string, string | string[]>]> {
|
||||||
|
const u = new URL(url);
|
||||||
|
return new Promise(resolve => {
|
||||||
|
https.get({
|
||||||
|
hostname: u.hostname,
|
||||||
|
path: u.pathname + u.search,
|
||||||
|
port: 443,
|
||||||
|
headers: {
|
||||||
|
Authorization: 'SSWS ' + this.dirConfig.token,
|
||||||
|
Accept: 'application/json',
|
||||||
|
},
|
||||||
|
}, res => {
|
||||||
|
let body = '';
|
||||||
|
|
||||||
|
res.on('data', chunk => {
|
||||||
|
body += chunk;
|
||||||
|
});
|
||||||
|
|
||||||
|
res.on('end', () => {
|
||||||
|
if (res.statusCode !== 200) {
|
||||||
|
resolve(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseJson = JSON.parse(body);
|
||||||
|
if (res.headers != null) {
|
||||||
|
const headersMap = new Map<string, string | string[]>();
|
||||||
|
for (const key in res.headers) {
|
||||||
|
if (res.headers.hasOwnProperty(key)) {
|
||||||
|
const val = res.headers[key];
|
||||||
|
headersMap.set(key.toLowerCase(), val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve([responseJson, headersMap]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve([responseJson, null]);
|
||||||
|
});
|
||||||
|
|
||||||
|
res.on('error', () => {
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async apiGetMany(endpoint: string, currentData: any[] = []): Promise<any[]> {
|
||||||
|
const url = endpoint.indexOf('https://') === 0 ? endpoint : `${this.dirConfig.orgUrl}/api/v1/${endpoint}`;
|
||||||
|
const response = await this.apiGetCall(url);
|
||||||
|
if (response == null || response[0] == null || !Array.isArray(response[0])) {
|
||||||
|
throw new Error('API call failed.');
|
||||||
|
}
|
||||||
|
if (response[0].length === 0) {
|
||||||
|
return currentData;
|
||||||
|
}
|
||||||
|
currentData = currentData.concat(response[0]);
|
||||||
|
if (response[1] == null) {
|
||||||
|
return currentData;
|
||||||
|
}
|
||||||
|
const linkHeader = response[1].get('link');
|
||||||
|
if (linkHeader == null || Array.isArray(linkHeader)) {
|
||||||
|
return currentData;
|
||||||
|
}
|
||||||
|
let nextLink: string = null;
|
||||||
|
const linkHeaderParts = linkHeader.split(',');
|
||||||
|
for (const part of linkHeaderParts) {
|
||||||
|
if (part.indexOf('; rel="next"') > -1) {
|
||||||
|
const subParts = part.split(';');
|
||||||
|
if (subParts.length > 0 && subParts[0].indexOf('https://') > -1) {
|
||||||
|
nextLink = subParts[0].replace('>', '').replace('<', '').trim();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nextLink == null) {
|
||||||
|
return currentData;
|
||||||
|
}
|
||||||
|
return this.apiGetMany(nextLink, currentData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
195
src/services/onelogin-directory.service.ts
Normal file
195
src/services/onelogin-directory.service.ts
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
import { DirectoryType } from '../enums/directoryType';
|
||||||
|
|
||||||
|
import { GroupEntry } from '../models/groupEntry';
|
||||||
|
import { OneLoginConfiguration } from '../models/oneLoginConfiguration';
|
||||||
|
import { SyncConfiguration } from '../models/syncConfiguration';
|
||||||
|
import { UserEntry } from '../models/userEntry';
|
||||||
|
|
||||||
|
import { BaseDirectoryService } from './baseDirectory.service';
|
||||||
|
import { ConfigurationService } from './configuration.service';
|
||||||
|
import { IDirectoryService } from './directory.service';
|
||||||
|
|
||||||
|
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||||
|
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||||
|
|
||||||
|
// Basic email validation: something@something.something
|
||||||
|
const ValidEmailRegex = /^\S+@\S+\.\S+$/;
|
||||||
|
|
||||||
|
export class OneLoginDirectoryService extends BaseDirectoryService implements IDirectoryService {
|
||||||
|
private dirConfig: OneLoginConfiguration;
|
||||||
|
private syncConfig: SyncConfiguration;
|
||||||
|
private accessToken: string;
|
||||||
|
private allUsers: any[] = [];
|
||||||
|
|
||||||
|
constructor(private configurationService: ConfigurationService, private logService: LogService,
|
||||||
|
private i18nService: I18nService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
|
||||||
|
const type = await this.configurationService.getDirectoryType();
|
||||||
|
if (type !== DirectoryType.OneLogin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dirConfig = await this.configurationService.getDirectory<OneLoginConfiguration>(DirectoryType.OneLogin);
|
||||||
|
if (this.dirConfig == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.syncConfig = await this.configurationService.getSync();
|
||||||
|
if (this.syncConfig == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.dirConfig.clientId == null || this.dirConfig.clientSecret == null) {
|
||||||
|
throw new Error(this.i18nService.t('dirConfigIncomplete'));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.accessToken = await this.getAccessToken();
|
||||||
|
if (this.accessToken == null) {
|
||||||
|
throw new Error('Could not get access token');
|
||||||
|
}
|
||||||
|
|
||||||
|
let users: UserEntry[];
|
||||||
|
if (this.syncConfig.users) {
|
||||||
|
users = await this.getUsers(force);
|
||||||
|
}
|
||||||
|
|
||||||
|
let groups: GroupEntry[];
|
||||||
|
if (this.syncConfig.groups) {
|
||||||
|
const setFilter = this.createCustomSet(this.syncConfig.groupFilter);
|
||||||
|
groups = await this.getGroups(this.forceGroup(force, users), setFilter);
|
||||||
|
users = this.filterUsersFromGroupsSet(users, groups, setFilter, this.syncConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [groups, users];
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getUsers(force: boolean): Promise<UserEntry[]> {
|
||||||
|
const entries: UserEntry[] = [];
|
||||||
|
const query = this.createDirectoryQuery(this.syncConfig.userFilter);
|
||||||
|
const setFilter = this.createCustomSet(this.syncConfig.userFilter);
|
||||||
|
this.logService.info('Querying users.');
|
||||||
|
this.allUsers = await this.apiGetMany('users' + (query != null ? '?' + query : ''));
|
||||||
|
this.allUsers.forEach(user => {
|
||||||
|
const entry = this.buildUser(user);
|
||||||
|
if (entry != null && !this.filterOutResult(setFilter, entry.email)) {
|
||||||
|
entries.push(entry);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Promise.resolve(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildUser(user: any) {
|
||||||
|
const entry = new UserEntry();
|
||||||
|
entry.externalId = user.id;
|
||||||
|
entry.referenceId = user.id;
|
||||||
|
entry.deleted = false;
|
||||||
|
entry.disabled = user.status === 2;
|
||||||
|
entry.email = user.email;
|
||||||
|
if (!this.validEmailAddress(entry.email) && user.username != null && user.username !== '') {
|
||||||
|
if (this.validEmailAddress(user.username)) {
|
||||||
|
entry.email = user.username;
|
||||||
|
} else if (this.syncConfig.useEmailPrefixSuffix && this.syncConfig.emailSuffix != null) {
|
||||||
|
entry.email = user.username + this.syncConfig.emailSuffix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (entry.email != null) {
|
||||||
|
entry.email = entry.email.trim().toLowerCase();
|
||||||
|
}
|
||||||
|
if (!this.validEmailAddress(entry.email)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getGroups(force: boolean, setFilter: [boolean, Set<string>]): Promise<GroupEntry[]> {
|
||||||
|
const entries: GroupEntry[] = [];
|
||||||
|
const query = this.createDirectoryQuery(this.syncConfig.groupFilter);
|
||||||
|
this.logService.info('Querying groups.');
|
||||||
|
const roles = await this.apiGetMany('roles' + (query != null ? '?' + query : ''));
|
||||||
|
roles.forEach(role => {
|
||||||
|
const entry = this.buildGroup(role);
|
||||||
|
if (entry != null && !this.filterOutResult(setFilter, entry.name)) {
|
||||||
|
entries.push(entry);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Promise.resolve(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildGroup(group: any) {
|
||||||
|
const entry = new GroupEntry();
|
||||||
|
entry.externalId = group.id;
|
||||||
|
entry.referenceId = group.id;
|
||||||
|
entry.name = group.name;
|
||||||
|
|
||||||
|
if (this.allUsers != null) {
|
||||||
|
this.allUsers.forEach(user => {
|
||||||
|
if (user.role_id != null && user.role_id.indexOf(entry.referenceId) > -1) {
|
||||||
|
entry.userMemberExternalIds.add(user.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getAccessToken() {
|
||||||
|
const response = await fetch(`https://api.${this.dirConfig.region}.onelogin.com/auth/oauth2/v2/token`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: new Headers({
|
||||||
|
'Authorization': 'Basic ' + btoa(this.dirConfig.clientId + ':' + this.dirConfig.clientSecret),
|
||||||
|
'Content-Type': 'application/json; charset=utf-8',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
}),
|
||||||
|
body: JSON.stringify({
|
||||||
|
grant_type: 'client_credentials',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (response.status === 200) {
|
||||||
|
const responseJson = await response.json();
|
||||||
|
if (responseJson.access_token != null) {
|
||||||
|
return responseJson.access_token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async apiGetCall(url: string): Promise<any> {
|
||||||
|
const req: RequestInit = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: new Headers({
|
||||||
|
Authorization: 'bearer:' + this.accessToken,
|
||||||
|
Accept: 'application/json',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const response = await fetch(new Request(url, req));
|
||||||
|
if (response.status === 200) {
|
||||||
|
const responseJson = await response.json();
|
||||||
|
return responseJson;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async apiGetMany(endpoint: string, currentData: any[] = []): Promise<any[]> {
|
||||||
|
const url = endpoint.indexOf('https://') === 0 ? endpoint :
|
||||||
|
`https://api.${this.dirConfig.region}.onelogin.com/api/1/${endpoint}`;
|
||||||
|
const response = await this.apiGetCall(url);
|
||||||
|
if (response == null || response.status == null || response.data == null) {
|
||||||
|
return currentData;
|
||||||
|
}
|
||||||
|
if (response.status.code !== 200) {
|
||||||
|
throw new Error('API call failed.');
|
||||||
|
}
|
||||||
|
currentData = currentData.concat(response.data);
|
||||||
|
if (response.pagination == null || response.pagination.next_link == null) {
|
||||||
|
return currentData;
|
||||||
|
}
|
||||||
|
return this.apiGetMany(response.pagination.next_link, currentData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private validEmailAddress(email: string) {
|
||||||
|
return email != null && email !== '' && ValidEmailRegex.test(email);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,31 +4,32 @@ import { GroupEntry } from '../models/groupEntry';
|
|||||||
import { SyncConfiguration } from '../models/syncConfiguration';
|
import { SyncConfiguration } from '../models/syncConfiguration';
|
||||||
import { UserEntry } from '../models/userEntry';
|
import { UserEntry } from '../models/userEntry';
|
||||||
|
|
||||||
import { ImportDirectoryRequest } from 'jslib/models/request/importDirectoryRequest';
|
import { OrganizationImportRequest } from 'jslib-common/models/request/organizationImportRequest';
|
||||||
import { ImportDirectoryRequestGroup } from 'jslib/models/request/importDirectoryRequestGroup';
|
|
||||||
import { ImportDirectoryRequestUser } from 'jslib/models/request/importDirectoryRequestUser';
|
|
||||||
|
|
||||||
import { ApiService } from 'jslib/abstractions/api.service';
|
import { ApiService } from 'jslib-common/abstractions/api.service';
|
||||||
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
|
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
||||||
import { LogService } from 'jslib/abstractions/log.service';
|
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||||
|
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||||
|
|
||||||
import { Utils } from 'jslib/misc/utils';
|
import { Utils } from 'jslib-common/misc/utils';
|
||||||
|
|
||||||
import { AzureDirectoryService } from './azure-directory.service';
|
import { AzureDirectoryService } from './azure-directory.service';
|
||||||
import { ConfigurationService } from './configuration.service';
|
import { ConfigurationService } from './configuration.service';
|
||||||
import { DirectoryService } from './directory.service';
|
import { IDirectoryService } from './directory.service';
|
||||||
import { GSuiteDirectoryService } from './gsuite-directory.service';
|
import { GSuiteDirectoryService } from './gsuite-directory.service';
|
||||||
import { LdapDirectoryService } from './ldap-directory.service';
|
import { LdapDirectoryService } from './ldap-directory.service';
|
||||||
import { OktaDirectoryService } from './okta-directory.service';
|
import { OktaDirectoryService } from './okta-directory.service';
|
||||||
|
import { OneLoginDirectoryService } from './onelogin-directory.service';
|
||||||
|
|
||||||
export class SyncService {
|
export class SyncService {
|
||||||
private dirType: DirectoryType;
|
private dirType: DirectoryType;
|
||||||
|
|
||||||
constructor(private configurationService: ConfigurationService, private logService: LogService,
|
constructor(private configurationService: ConfigurationService, private logService: LogService,
|
||||||
private cryptoFunctionService: CryptoFunctionService, private apiService: ApiService,
|
private cryptoFunctionService: CryptoFunctionService, private apiService: ApiService,
|
||||||
private messagingService: MessagingService, private i18nService: I18nService) { }
|
private messagingService: MessagingService, private i18nService: I18nService,
|
||||||
|
private environmentService: EnvironmentService) { }
|
||||||
|
|
||||||
async sync(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
|
async sync(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
|
||||||
this.dirType = await this.configurationService.getDirectoryType();
|
this.dirType = await this.configurationService.getDirectoryType();
|
||||||
@@ -50,13 +51,22 @@ export class SyncService {
|
|||||||
try {
|
try {
|
||||||
const entries = await directoryService.getEntries(force || syncConfig.overwriteExisting, test);
|
const entries = await directoryService.getEntries(force || syncConfig.overwriteExisting, test);
|
||||||
let groups = entries[0];
|
let groups = entries[0];
|
||||||
let users = entries[1];
|
let users = this.filterUnsupportedUsers(entries[1]);
|
||||||
|
|
||||||
if (groups != null && groups.length > 0) {
|
if (groups != null && groups.length > 0) {
|
||||||
this.flattenUsersToGroups(groups, groups);
|
this.flattenUsersToGroups(groups, groups);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (test || ((groups == null || groups.length === 0) && (users == null || users.length === 0))) {
|
const duplicateEmails = this.findDuplicateUserEmails(users);
|
||||||
|
if (duplicateEmails.length > 0) {
|
||||||
|
const emailsMessage = duplicateEmails.length < 4 ?
|
||||||
|
duplicateEmails.join('\n') :
|
||||||
|
duplicateEmails.slice(0, 3).join('\n') + '\n' + this.i18nService.t('andMore', `${duplicateEmails.length - 3}`);
|
||||||
|
throw new Error(this.i18nService.t('duplicateEmails') + '\n' + emailsMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (test || (!syncConfig.overwriteExisting &&
|
||||||
|
(groups == null || groups.length === 0) && (users == null || users.length === 0))) {
|
||||||
if (!test) {
|
if (!test) {
|
||||||
await this.saveSyncTimes(syncConfig, now);
|
await this.saveSyncTimes(syncConfig, now);
|
||||||
}
|
}
|
||||||
@@ -65,23 +75,29 @@ export class SyncService {
|
|||||||
return [groups, users];
|
return [groups, users];
|
||||||
}
|
}
|
||||||
|
|
||||||
const req = this.buildRequest(groups, users, syncConfig.removeDisabled, syncConfig.overwriteExisting);
|
const req = this.buildRequest(groups, users, syncConfig.removeDisabled, syncConfig.overwriteExisting, syncConfig.largeImport);
|
||||||
const reqJson = JSON.stringify(req);
|
const reqJson = JSON.stringify(req);
|
||||||
|
|
||||||
|
const orgId = await this.configurationService.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;
|
let hash: string = null;
|
||||||
const hashBuf = await this.cryptoFunctionService.hash(this.apiService.apiBaseUrl + reqJson, 'sha256');
|
const hashBuff = await this.cryptoFunctionService.hash(this.environmentService.getApiUrl() + orgId + reqJson, 'sha256');
|
||||||
if (hashBuf != null) {
|
if (hashBuff != null) {
|
||||||
hash = Utils.fromBufferToB64(hashBuf);
|
hash = Utils.fromBufferToB64(hashBuff);
|
||||||
}
|
}
|
||||||
const lastHash = await this.configurationService.getLastSyncHash();
|
const lastHash = await this.configurationService.getLastSyncHash();
|
||||||
|
|
||||||
if (lastHash == null || hash !== lastHash) {
|
if (lastHash == null || (hash !== lastHash && hashLegacy !== lastHash)) {
|
||||||
const orgId = await this.configurationService.getOrganizationId();
|
await this.apiService.postPublicImportDirectory(req);
|
||||||
if (orgId == null) {
|
|
||||||
throw new Error('Organization not set.');
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.apiService.postImportDirectory(orgId, req);
|
|
||||||
await this.configurationService.saveLastSyncHash(hash);
|
await this.configurationService.saveLastSyncHash(hash);
|
||||||
} else {
|
} else {
|
||||||
groups = null;
|
groups = null;
|
||||||
@@ -102,18 +118,38 @@ export class SyncService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private findDuplicateUserEmails(users: UserEntry[]) {
|
||||||
|
const duplicatedEmails = new Array<string>();
|
||||||
|
users.reduce((agg, user) => {
|
||||||
|
if (agg.includes(user.email) && !duplicatedEmails.includes(user.email)) {
|
||||||
|
duplicatedEmails.push(user.email);
|
||||||
|
} else {
|
||||||
|
agg.push(user.email);
|
||||||
|
}
|
||||||
|
return agg;
|
||||||
|
}, new Array<string>());
|
||||||
|
return duplicatedEmails;
|
||||||
|
}
|
||||||
|
|
||||||
|
private filterUnsupportedUsers(users: UserEntry[]): UserEntry[] {
|
||||||
|
return users == null ? null : users.filter(u => u.email?.length <= 256);
|
||||||
|
}
|
||||||
|
|
||||||
private flattenUsersToGroups(levelGroups: GroupEntry[], allGroups: GroupEntry[]): Set<string> {
|
private flattenUsersToGroups(levelGroups: GroupEntry[], allGroups: GroupEntry[]): Set<string> {
|
||||||
let allUsers = new Set<string>();
|
let allUsers = new Set<string>();
|
||||||
|
if (allGroups == null) {
|
||||||
|
return allUsers;
|
||||||
|
}
|
||||||
for (const group of levelGroups) {
|
for (const group of levelGroups) {
|
||||||
const childGroups = allGroups.filter((g) => group.groupMemberReferenceIds.has(g.referenceId));
|
const childGroups = allGroups.filter(g => group.groupMemberReferenceIds.has(g.referenceId));
|
||||||
const childUsers = this.flattenUsersToGroups(childGroups, allGroups);
|
const childUsers = this.flattenUsersToGroups(childGroups, allGroups);
|
||||||
childUsers.forEach((id) => group.userMemberExternalIds.add(id));
|
childUsers.forEach(id => group.userMemberExternalIds.add(id));
|
||||||
allUsers = new Set([...allUsers, ...group.userMemberExternalIds]);
|
allUsers = new Set([...allUsers, ...group.userMemberExternalIds]);
|
||||||
}
|
}
|
||||||
return allUsers;
|
return allUsers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getDirectoryService(): DirectoryService {
|
private getDirectoryService(): IDirectoryService {
|
||||||
switch (this.dirType) {
|
switch (this.dirType) {
|
||||||
case DirectoryType.GSuite:
|
case DirectoryType.GSuite:
|
||||||
return new GSuiteDirectoryService(this.configurationService, this.logService, this.i18nService);
|
return new GSuiteDirectoryService(this.configurationService, this.logService, this.i18nService);
|
||||||
@@ -123,40 +159,33 @@ export class SyncService {
|
|||||||
return new LdapDirectoryService(this.configurationService, this.logService, this.i18nService);
|
return new LdapDirectoryService(this.configurationService, this.logService, this.i18nService);
|
||||||
case DirectoryType.Okta:
|
case DirectoryType.Okta:
|
||||||
return new OktaDirectoryService(this.configurationService, this.logService, this.i18nService);
|
return new OktaDirectoryService(this.configurationService, this.logService, this.i18nService);
|
||||||
|
case DirectoryType.OneLogin:
|
||||||
|
return new OneLoginDirectoryService(this.configurationService, this.logService, this.i18nService);
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildRequest(groups: GroupEntry[], users: UserEntry[], removeDisabled: boolean,
|
private buildRequest(groups: GroupEntry[], users: UserEntry[], removeDisabled: boolean, overwriteExisting: boolean,
|
||||||
overwriteExisting: boolean): ImportDirectoryRequest {
|
largeImport: boolean = false) {
|
||||||
const model = new ImportDirectoryRequest();
|
return new OrganizationImportRequest({
|
||||||
model.overwriteExisting = overwriteExisting;
|
groups: (groups ?? []).map(g => {
|
||||||
|
return {
|
||||||
if (groups != null) {
|
name: g.name,
|
||||||
for (const g of groups) {
|
externalId: g.externalId,
|
||||||
const ig = new ImportDirectoryRequestGroup();
|
memberExternalIds: Array.from(g.userMemberExternalIds),
|
||||||
ig.name = g.name;
|
};
|
||||||
ig.externalId = g.externalId;
|
}),
|
||||||
ig.users = Array.from(g.userMemberExternalIds);
|
users: (users ?? []).map(u => {
|
||||||
model.groups.push(ig);
|
return {
|
||||||
}
|
email: u.email,
|
||||||
}
|
externalId: u.externalId,
|
||||||
|
deleted: u.deleted || (removeDisabled && u.disabled),
|
||||||
if (users != null) {
|
};
|
||||||
for (const u of users) {
|
}),
|
||||||
const iu = new ImportDirectoryRequestUser();
|
overwriteExisting: overwriteExisting,
|
||||||
iu.email = u.email;
|
largeImport: largeImport,
|
||||||
if (iu.email != null) {
|
});
|
||||||
iu.email = iu.email.trim().toLowerCase();
|
|
||||||
}
|
|
||||||
iu.externalId = u.externalId;
|
|
||||||
iu.deleted = u.deleted || (removeDisabled && u.disabled);
|
|
||||||
model.users.push(iu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return model;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async saveSyncTimes(syncConfig: SyncConfiguration, time: Date) {
|
private async saveSyncTimes(syncConfig: SyncConfiguration, time: Date) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||||
|
|
||||||
import { SyncService } from './services/sync.service';
|
import { SyncService } from './services/sync.service';
|
||||||
|
|
||||||
|
|||||||
@@ -8,58 +8,32 @@
|
|||||||
"target": "ES2016",
|
"target": "ES2016",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
"types": [],
|
"types": [],
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"tldjs": [
|
"tldjs": [
|
||||||
"jslib/src/misc/tldjs.noop"
|
"jslib/src/misc/tldjs.noop"
|
||||||
],
|
],
|
||||||
"jslib/*": [
|
"jslib-common/*": [
|
||||||
"jslib/src/*"
|
"jslib/common/src/*"
|
||||||
],
|
],
|
||||||
"@angular/*": [
|
"jslib-angular/*": [
|
||||||
"node_modules/@angular/*"
|
"jslib/angular/src/*"
|
||||||
],
|
],
|
||||||
"electron": [
|
"jslib-electron/*": [
|
||||||
"node_modules/electron"
|
"jslib/electron/src/*"
|
||||||
],
|
],
|
||||||
"node": [
|
"jslib-node/*": [
|
||||||
"node_modules/@types/node"
|
"jslib/node/src/*"
|
||||||
],
|
|
||||||
"duo_web_sdk": [
|
|
||||||
"node_modules/duo_web_sdk"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
"preserveWhitespaces": true
|
"preserveWhitespaces": true
|
||||||
},
|
},
|
||||||
"exclude": [
|
"include": [
|
||||||
"node_modules",
|
"src",
|
||||||
"jslib/node_modules",
|
"src-cli"
|
||||||
"jslib/src/services/index.ts",
|
|
||||||
"jslib/src/services/webCryptoFunction.service.ts",
|
|
||||||
"jslib/src/services/search.service.ts",
|
|
||||||
"jslib/src/services/nodeApi.service.ts",
|
|
||||||
"jslib/src/services/lowdbStorage.service.ts",
|
|
||||||
"jslib/src/services/export.service.ts",
|
|
||||||
"jslib/src/services/notifications.service.ts",
|
|
||||||
"jslib/src/services/passwordGeneration.service.ts",
|
|
||||||
"jslib/src/abstractions/index.ts",
|
|
||||||
"jslib/src/abstractions/passwordGeneration.service.ts",
|
|
||||||
"jslib/src/angular/components/export.component.ts",
|
|
||||||
"jslib/src/angular/components/register.component.ts",
|
|
||||||
"jslib/src/angular/components/add-edit.component.ts",
|
|
||||||
"jslib/src/angular/components/password-generator.component.ts",
|
|
||||||
"jslib/src/angular/components/password-generator-history.component.ts",
|
|
||||||
"jslib/src/angular/pipes/color-password.pipe.ts",
|
|
||||||
"jslib/src/angular/directives/select-copy.directive.ts",
|
|
||||||
"jslib/src/importers",
|
|
||||||
"dist",
|
|
||||||
"dist-cli",
|
|
||||||
"jslib/dist",
|
|
||||||
"build",
|
|
||||||
"build-cli",
|
|
||||||
"jslib/spec"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
23
tslint.json
23
tslint.json
@@ -49,6 +49,27 @@
|
|||||||
"check-separator",
|
"check-separator",
|
||||||
"check-type"
|
"check-type"
|
||||||
],
|
],
|
||||||
"max-classes-per-file": false
|
"max-classes-per-file": false,
|
||||||
|
"ordered-imports": true,
|
||||||
|
"arrow-parens": [
|
||||||
|
true,
|
||||||
|
"ban-single-arg-parens"
|
||||||
|
],
|
||||||
|
"semicolon": [
|
||||||
|
true,
|
||||||
|
"always"
|
||||||
|
],
|
||||||
|
"trailing-comma": [
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
"multiline": {
|
||||||
|
"objects": "always",
|
||||||
|
"arrays": "always",
|
||||||
|
"functions": "ignore",
|
||||||
|
"typeLiterals": "ignore"
|
||||||
|
},
|
||||||
|
"singleline": "never"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
const nodeExternals = require('webpack-node-externals');
|
const nodeExternals = require('webpack-node-externals');
|
||||||
|
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
||||||
|
|
||||||
if (process.env.NODE_ENV == null) {
|
if (process.env.NODE_ENV == null) {
|
||||||
process.env.NODE_ENV = 'development';
|
process.env.NODE_ENV = 'development';
|
||||||
@@ -27,12 +28,12 @@ const moduleRules = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const plugins = [
|
const plugins = [
|
||||||
new CleanWebpackPlugin([
|
new CleanWebpackPlugin(),
|
||||||
path.resolve(__dirname, 'build-cli/*'),
|
new CopyWebpackPlugin({
|
||||||
]),
|
patterns: [
|
||||||
new CopyWebpackPlugin([
|
{ from: './src/locales', to: 'locales' },
|
||||||
{ from: './src/locales', to: 'locales' },
|
],
|
||||||
]),
|
}),
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'process.env.BWCLI_ENV': JSON.stringify(ENV),
|
'process.env.BWCLI_ENV': JSON.stringify(ENV),
|
||||||
}),
|
}),
|
||||||
@@ -59,12 +60,7 @@ const config = {
|
|||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.ts', '.js', '.json'],
|
extensions: ['.ts', '.js', '.json'],
|
||||||
alias: {
|
plugins: [new TsconfigPathsPlugin({ configFile: './tsconfig.json' })],
|
||||||
jslib: path.join(__dirname, 'jslib/src'),
|
|
||||||
tldjs: path.join(__dirname, 'jslib/src/misc/tldjs.noop'),
|
|
||||||
// ref: https://github.com/bitinn/node-fetch/issues/493
|
|
||||||
'node-fetch$': 'node-fetch/lib/index.js',
|
|
||||||
},
|
|
||||||
symlinks: false,
|
symlinks: false,
|
||||||
modules: [path.resolve('node_modules')],
|
modules: [path.resolve('node_modules')],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const merge = require('webpack-merge');
|
const { merge } = require('webpack-merge');
|
||||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||||
const nodeExternals = require('webpack-node-externals');
|
const nodeExternals = require('webpack-node-externals');
|
||||||
|
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
||||||
|
|
||||||
const common = {
|
const common = {
|
||||||
module: {
|
module: {
|
||||||
@@ -22,10 +23,7 @@ const common = {
|
|||||||
plugins: [],
|
plugins: [],
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.tsx', '.ts', '.js'],
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
alias: {
|
plugins: [new TsconfigPathsPlugin({ configFile: './tsconfig.json' })],
|
||||||
jslib: path.join(__dirname, 'jslib/src'),
|
|
||||||
tldjs: path.join(__dirname, 'jslib/src/misc/tldjs.noop'),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
filename: '[name].js',
|
filename: '[name].js',
|
||||||
@@ -55,14 +53,14 @@ const main = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new CleanWebpackPlugin([
|
new CleanWebpackPlugin(),
|
||||||
path.resolve(__dirname, 'build/*'),
|
new CopyWebpackPlugin({
|
||||||
]),
|
patterns: [
|
||||||
new CopyWebpackPlugin([
|
'./src/package.json',
|
||||||
'./src/package.json',
|
{ from: './src/images', to: 'images' },
|
||||||
{ from: './src/images', to: 'images' },
|
{ from: './src/locales', to: 'locales' },
|
||||||
{ from: './src/locales', to: 'locales' },
|
],
|
||||||
]),
|
}),
|
||||||
],
|
],
|
||||||
externals: [nodeExternals()],
|
externals: [nodeExternals()],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const merge = require('webpack-merge');
|
const { merge } = require('webpack-merge');
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
const AngularCompilerPlugin = require('@ngtools/webpack').AngularCompilerPlugin;
|
const AngularCompilerPlugin = require('@ngtools/webpack').AngularCompilerPlugin;
|
||||||
|
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
||||||
const extractCss = new ExtractTextPlugin({
|
|
||||||
filename: '[name].css',
|
|
||||||
disable: false,
|
|
||||||
allChunks: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const common = {
|
const common = {
|
||||||
module: {
|
module: {
|
||||||
@@ -39,9 +34,7 @@ const common = {
|
|||||||
plugins: [],
|
plugins: [],
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.tsx', '.ts', '.js', '.json'],
|
extensions: ['.tsx', '.ts', '.js', '.json'],
|
||||||
alias: {
|
plugins: [new TsconfigPathsPlugin({ configFile: './tsconfig.json' })],
|
||||||
jslib: path.join(__dirname, 'jslib/src'),
|
|
||||||
},
|
|
||||||
symlinks: false,
|
symlinks: false,
|
||||||
modules: [path.resolve('node_modules')],
|
modules: [path.resolve('node_modules')],
|
||||||
},
|
},
|
||||||
@@ -53,6 +46,7 @@ const common = {
|
|||||||
|
|
||||||
const renderer = {
|
const renderer = {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
|
devtool: false,
|
||||||
target: 'electron-renderer',
|
target: 'electron-renderer',
|
||||||
node: {
|
node: {
|
||||||
__dirname: false,
|
__dirname: false,
|
||||||
@@ -93,17 +87,16 @@ const renderer = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.scss$/,
|
test: /\.scss$/,
|
||||||
use: extractCss.extract({
|
use: [
|
||||||
use: [
|
{
|
||||||
{
|
loader: MiniCssExtractPlugin.loader,
|
||||||
loader: 'css-loader',
|
options: {
|
||||||
|
publicPath: '../',
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
loader: 'sass-loader',
|
'css-loader',
|
||||||
},
|
'sass-loader',
|
||||||
],
|
],
|
||||||
publicPath: '../',
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
// Hide System.import warnings. ref: https://github.com/angular/angular/issues/21560
|
// Hide System.import warnings. ref: https://github.com/angular/angular/issues/21560
|
||||||
{
|
{
|
||||||
@@ -127,11 +120,13 @@ const renderer = {
|
|||||||
chunks: ['app/vendor', 'app/main']
|
chunks: ['app/vendor', 'app/main']
|
||||||
}),
|
}),
|
||||||
new webpack.SourceMapDevToolPlugin({
|
new webpack.SourceMapDevToolPlugin({
|
||||||
filename: '[name].js.map',
|
|
||||||
include: ['app/main.js']
|
include: ['app/main.js']
|
||||||
}),
|
}),
|
||||||
new webpack.DefinePlugin({ 'global.GENTLY': false }),
|
new webpack.DefinePlugin({ 'global.GENTLY': false }),
|
||||||
extractCss,
|
new MiniCssExtractPlugin({
|
||||||
|
filename: '[name].[hash].css',
|
||||||
|
chunkFilename: '[id].[hash].css',
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user