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

Compare commits

...

89 Commits

Author SHA1 Message Date
Matt Gibson
8072c523cc Bump patch version to denote no major features released (#145)
(cherry picked from commit bb1cdebaf4)
2021-08-18 16:40:50 -04:00
Matt Gibson
f96429a47b Version bump to 2.10.0 (#143)
(cherry picked from commit 01405f47c9)
2021-08-17 15:09:49 -04:00
Thomas Rittson
5e64dc9262 Update jslib (#142) 2021-08-11 13:02:38 +10:00
Michael Klapper
9c7cd943b3 Update Administrative Units API Endpoint (#125)
https://docs.microsoft.com/en-us/graph/api/administrativeunit-list-members?view=graph-rest-1.0#list-member-objects
2021-07-28 12:42:42 -05:00
Oscar Hinton
7cf3166169 Add support for helpers in environment service (#139)
* Add support for helpers in environment service

* Bump jslib
2021-07-23 17:15:35 -04:00
Daniel James Smith
9bdb77a573 Add node version to requirements in README.md (#117)
* Add node version to requirements in README.md

* Update README.md

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
2021-07-01 06:16:56 +10:00
Vincent Salucci
3b8ee5ec0d [Version] Bump to 2.9.3 (#138) 2021-06-28 12:49:43 -05:00
Matt Gibson
6e7e09064f Error on duplicate emails (#136)
* Allow main debugging in development builds

* Early fail on attempting to sync multiple users with the same email

* Truncate duplicate list if greater than 3

* Revert "Allow main debugging in development builds"

This reverts commit 3b804dd959.
2021-06-24 14:35:12 -05:00
Thomas Rittson
dfcb450a8a Merge pull request #131 from luc-bw/toggle-AAD-key
Use password fields with visibility toggle for sensitive data
2021-06-23 12:05:11 +10:00
Thomas Rittson
b192c34c15 Fix linting 2021-06-23 12:01:01 +10:00
Thomas Rittson
f813dbb690 Simplify ngClass attributes, add missing styling 2021-06-23 11:59:49 +10:00
Thomas Rittson
16deafca76 Merge branch 'master' into toggle-AAD-key 2021-06-23 11:00:09 +10:00
Matt Gibson
647b087fa7 Refresh token with api key (#135)
* Do not persist client creds on logout

* Override refreshing token flow with re-authentication flow

* Update jslib

* PR review comments
2021-06-22 15:13:08 -05:00
Luc
4bd1387b83 requested updates 2021-06-21 19:18:14 -07:00
Thomas Rittson
4e098462dc Merge pull request #133 from bitwarden/revert-ldapjs
Revert ldapjs update, use forked repo instead
2021-06-16 16:02:52 -07:00
Thomas Rittson
0b1c2ae72a Revert ldapjs update, use forked repo instead 2021-06-15 11:10:09 +10:00
Matt Gibson
5d3fa0a0d2 Improve okta group performance (#132)
* Avoid unnecessary API calls to Okta

Filter excluded/included groups as early as possible to avoid using up
API calls and long waits

* Remove console timing calls
2021-06-11 11:10:29 -05:00
Oscar Hinton
6097bca063 Add jslib as a "real" dependency (#127)
* Split jslib

* Change hook to preinstall

* Install gyp (ci)

* Fix rebuild command

* Review comments

* Add tsconfig-paths-plugin to webpack.cli.

* Bump jslib

* Install old version of prebuild-install to bypass bug in pkg
2021-06-09 21:46:38 +02:00
Luc
a6aafe7593 Add visibility toggle to secrets
Added visibility toggle to login and directory secrets
2021-06-08 15:53:44 -07:00
Matt Gibson
56d05af07a Use organization api key for auth (#121)
* Use api key for login

* Remove user login and organization setting

* Override Api authentication to expect organization keys

* Linter fixes

* Use public API

The organization api key is valid only in the public api scope

* Use organization api key in CLI utility

* Serialize storageService writes

* Prefer multiple awaits to .then chains

* Initial PR review

* Do not treat api key inputs as passwords

This conforms with how they are handled in CLI/web

* Update jslib

* PR feedback
2021-06-02 13:43:18 -05:00
Joseph Flinn
0d17345600 constraining release to the rc branch (#126) 2021-06-01 12:01:37 -07:00
Thomas Rittson
5df62b7422 Merge pull request #122 from bitwarden/clean-exit
Add --cleanexit option
2021-05-26 21:16:39 +10:00
Thomas Rittson
868914feb1 bump juslib 2021-05-26 09:19:32 +10:00
Thomas Rittson
1a9555d4af add --cleanexit option 2021-05-25 10:59:29 +10:00
Matt Gibson
33c8f15e45 OneLogin uses Roles instead of Groups (#118) 2021-05-20 15:45:49 -05:00
Matt Gibson
ed8dd01dbd Add option to bypass large import limit of 2000 users (#119)
* Add option to bypass large import limit of 2000 users

Also add orgId to last sync hash

* Update jslib

* PR review

* Update src/services/sync.service.ts

Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com>

Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com>
2021-05-20 11:59:54 -05:00
Vince Grassia
2296e37e8f Pin versions of actions in workflow (#120) 2021-05-17 11:19:05 -04:00
Oscar Hinton
a0f33c7bdc Bump node to 14 (#116)
* Bump node to 14

* Add engines

* Bump dependencies

* Change engine to ~14.

* Bump jslib
2021-05-12 22:38:32 +02:00
Oscar Hinton
f6b249836e Bump dependencies (#114)
* Upgrade angular and webpack dependencies

* Bump microsoft-graph-client and googleapis

* Bump pkg-fetch in pipeline

* Bump jslib
2021-04-23 21:03:59 +02:00
Thomas Rittson
0c92a97054 Merge pull request #111 from bitwarden/update-ldapjs
Update ldapjs dependency
2021-04-23 08:17:47 +10:00
Vincent Salucci
24ab152559 [Version] Bump to 2.9.2 (#115)
* [Version] Bump to 2.10.0

* Downgraded to 2.9.2
2021-04-22 12:37:52 -05:00
Thomas Rittson
5f9f09d77c Merge pull request #112 from bitwarden/hotfix/expressionChangedAfterItHasBeenCheckedError
Resolve ExpressionChangedAfterItHasBeenCheckedError
2021-04-22 19:06:16 +10:00
Hinton
4d27d9e48d Update simbtn. 2021-04-22 10:09:19 +02:00
Matt Gibson
0b624b972a Update jslib (#113)
* Update jslib

* Update jslib
2021-04-21 14:24:39 -05:00
Hinton
1889e12bac Resolve ExpressionChangedAfterItHasBeenCheckedError 2021-04-21 09:35:12 +02:00
Thomas Rittson
7648f73072 Update @types/ldapjs dependency 2021-04-21 14:46:56 +10:00
Thomas Rittson
75d346ed85 update ldapjs dependency 2021-04-21 13:28:29 +10:00
Oscar Hinton
dabfe7907d Remove last remnants of old analytics code (#110) 2021-04-14 23:42:51 +02:00
Kyle Spearrin
965976223f Revert "update google apis"
This reverts commit 410f00c213.
2021-04-13 15:23:32 -04:00
Kyle Spearrin
410f00c213 update google apis 2021-04-13 15:03:20 -04:00
Kyle Spearrin
0d8b942ad4 npm audit fix 2021-04-13 14:57:46 -04:00
Kyle Spearrin
5371015a58 update libs 2021-04-13 14:56:17 -04:00
Oscar Hinton
2ead70e434 Bump jslib (#108) 2021-04-07 20:42:39 +02:00
Thomas Rittson
ffca14cb5f Merge pull request #107 from bitwarden/fix-ad-not-removing-users
Fix AD sync not overwriting removed users
2021-04-01 06:48:32 +10:00
Thomas Rittson
090d5e82df Send empty sync to server if overwriteExisting 2021-03-30 12:37:48 +10:00
Chad Scharf
8893ddf0f7 Merge pull request #106 from djsmith85/master
Fix filteringForUnsupportedUsers and extend email validation to handle emails up to 256 char
2021-03-25 14:23:26 -04:00
Daniel James Smith
997ec5a699 Extend validation to handle emails up to 256 char 2021-03-25 18:39:05 +01:00
Daniel James Smith
762818ee39 Fix filtering unsupported users 2021-03-25 18:36:43 +01:00
Oscar Hinton
61c6ba8189 Bump electron to 11.3.0 (#104) 2021-03-15 23:19:30 +01:00
Thomas Rittson
9cfa646bcb Merge pull request #102 from bitwarden/cert-empty-subject
Fix handling of empty subject names in certs
2021-03-11 13:11:36 +10:00
Thomas Rittson
b4301c7d41 Fix handling of empty subject names in certs 2021-03-11 12:43:29 +10:00
Chad Scharf
71b5f6a38a Merge pull request #101 from bitwarden/version-bump
Patch release version bump to 2.9.1
2021-03-10 12:13:32 -05:00
Thomas Rittson
1c0052fe30 Patch release version bump to 2.9.1 2021-03-10 09:05:02 +10:00
Matt Gibson
35862acb73 Update jslib (#100) 2021-03-09 11:33:45 -06:00
Chad Scharf
11cf64fcc7 Merge pull request #99 from bitwarden/fix/deleted-user-fail
Don't check user group filter for deleted users
2021-03-05 18:04:01 -05:00
Chad Scharf
2ab37b45cf Don't check user group filter for deleted users 2021-03-05 15:49:57 -05:00
Joseph Flinn
7096fc830b adding the build assets for the rc branch (#98) 2021-03-04 10:25:23 -08:00
Matt Gibson
39806b7d96 Update jslib (#97) 2021-03-02 13:30:11 -06:00
Kyle Spearrin
7a16b8cb0e Update README.md 2021-02-17 13:17:25 -05:00
Matt Gibson
2583068dbd Lock lowdb file (#95)
* Lock lowdb file when using. Do not allow caching

* Linter fixes

* Move to non-jslib lowdbstorage to allow for lockfile

* update jslib

* Must ensure db file exists prior to initialization

proper-lockfile throws if the file its locking does not exist

* update jslib

* Let base handle file initialization
2021-02-17 10:33:05 -06:00
Matt Gibson
e5d0b3a372 Update to commander 7 (#94)
* Upgrade to commander 7.0.0

* Match lint rules for typescript

* update jslib
2021-02-08 13:32:02 -06:00
Oscar Hinton
9a1caf1e7e Update electron to 11.1.1 (#85) 2021-02-03 23:08:50 +01:00
Kyle Spearrin
af0e41e26c dont catch api error and return false (#93) 2021-02-03 15:58:37 -05:00
Joseph Flinn
d3049164a9 Migrate to gh actions (#89)
* intial go at building the windows pipeline in GH

* fixing whitespace issue

* moving version info script

* changing the electron-builder commands to the npm scripts

* fixing the PACKAGE_VERSION var

* adding debugging statements

* changing list command

* fixing PACKAGE_VERSION var

* adding linux job and disabling windows job

* debugging linux installs

* retrying the rpm

* re-enabling the windows build

* re-enabling publishing of the exe

* debugging pkg fetched

* debugging this more

* testing install of pkg-fetch with npm

* moving pkg-fetch installation

* trying to manually add the fetched package

* I was wrong. This wasn't linux. Switching to pwsh

* fixing the pwsh var syntax

* removing debugging tasks and re-enabling the other build tasks

* adding build_and_signing. Removing the non-cli executables from the build pipeline and disabling it for testing.

* removing some whitespace

* switching how we get package version

* adding custom signing script

* removing deubbing code and getting ready for PR

* adding in another release gate

* chaning file name to fit previous standards

* removing appveyor pipeline file

* moving all of the build tasks to the same build file

* changing GITHUB_TOKEN because GITHUB_* is probably reserved

* adding release pipeline and moving all realease tasks to that pipeline

* updating the package.json's to contain the releases to my repo

* fixing the RELEASE_TAG_NAME and switching the electron builder from pack to publish

* fixing the npm run publish command

* adding GH_TOKEN to the build and sign task

* fixing upload path

* removing the release asset upload since I think they are already published?

* removing testing code

* testing tweak to github release

* making sure I've got the right repo set

* removing whitespace

* adding in clone task to setup

* removing the stop-gap

* adding GH_TOKEN to the linux publish task

* fixing string

* switching to manual publishing. There seems to be a bug in the electron-builder publishing? or our setup

* switching back to electron-builder publishing but manually creating and pushing the tag

* I don't know why electron-builder isn't picking up the release. Adding some debugging code

* adding in GH token for release checking

* adding another GH token for release checking

* commenting out the tagging portion. This should just happen automatically...

* trying the release without the manual uploads?

* adding -d flag to release edit

* disabling the gui build to see if the cli changes the tag

* trying out a fix

* testing the upload release asset action

* fixing typo

* trying RELEASE_NAME

* fixing bash error

* trying something else for the release name

* changing all of the release asset uploads to a provided action

* Removing some debugging code

* re-enabling the windows and linux jobs

* changing the content type of the checksum files

* fixing typo

* removing the PKG_INFO flag

* installing RH with choco

* testing the reshack

* reenabling the correct job

* resetting release workflow and adding exp workflow

* trying ResourceHacker.exe

* switching to pwsh to see if that works

* switching back and specifying cmd shell

* finding the bin to add to the path

* wrestling with cmd

* debugging path

* giving up on nice printing

* changing to different path debugging

* adding RH to the path

* trying something else

* trying something else

* maybe the path resets?

* updating exp workflow to try to get reshack to work

* trying to add to the path without the quotes

* fixing the RH test

* debugging path

* setting path forever

* not playing around with perfect environment paths with windows....

* preivous test was inconclusive

* testing RH

* changing the npm command and removing unnecssary GITHUB_TOKEN

* removing the exp workflow

* quoting the signing file

* debugging VER_INFO

* debugging the pkg-fetch

* disabling non-cli jobs

* changing value of WIN_PKG

* testing more pkg-fetch

* changing the paths to the home directory

* renaming exp workflow

* trying a string

* trying it from the home directory

* removing the stop gap

* updating the version to something that RH supports

* initial release test

* fixing GITHUB_TOKEN

* changing the version to a real version

* debugging tag names

* changing the trigger on the exp workflow

* moving the disabled job to the correct workflow

* trying wet spaghetti

* updating case statement

* adding in the findings from the experiment

* removing testing code. Leaving unfinished macos build disabled

* removing the prod environment secrets

* setting up the mac build job

* renaming the key name

* moving the signing file

* working on the mac packaging

* removing desktop mac certs

* disabling the non-mac jobs

* setting up the build workflow for first run

* adding manual trigger to the build workflow

* disabling the push trigger

* removing the non-existant setup function

* removing the unneeded certs

* removing increment version since we are not submitting to the Apple Store.

* re-enabling the APPLE_ID vars

* updating how the package version is retrieved in build. staging release workflow for testing

* fixing the asset upload updating the repo in package.json

* adding debugging to dist

* adding in missing directory for debugging

* renaming that file

* updating the build/release workflows

* fixing the setup output

* updating file name and changing dist to publish

* adding in the missing token

* changing the zip name

* add debuggin

* fixing debugging step

* removing debugging task. Not needed

* reworking the content type of the mac release assets

* removing the rename task and adding in some debugging

* flipping the order of the dmg and the mac.zip upload to see if it is a problem with the release asset upload

* adding the renaming back in

* switching the upload name back to dashes

* commenting out the manual release asset upload. Looks like publish is doing that?

* removing all debugging code

* updating README with the GitHub Actions Badge

* changing all of the slashes to match

* removing unneeded package version setting

* removing unneeded package version setup

* adding WIN_PKG task back in. accidentally removed it
2021-02-03 09:12:43 -08:00
Thomas Rittson
bdfda6775d Merge pull request #90 from bitwarden/bump-https-proxy-agent
Bump https proxy agent
2021-02-02 12:59:25 +10:00
Thomas Rittson
72e6e74a42 add allowSyntheticDefaultImports in tsconfig 2021-02-02 10:32:18 +10:00
Thomas Rittson
75832fbed9 bump https-proxy-agent verison to 5.0 2021-02-01 16:50:47 +10:00
Thomas Rittson
811b3adb51 Update jslib 2021-02-01 16:31:16 +10:00
Kyle Spearrin
e710df3ba7 certs for CI 2021-01-29 12:29:30 -05:00
Chad Scharf
4ee0c3ccba Merge pull request #87 from bitwarden/version-bump-2.9.0
version bump 2.9.0
2021-01-19 16:18:08 -05:00
Chad Scharf
036b934119 version bump 2.9.0 2021-01-19 15:15:15 -05:00
Vincent Salucci
4bfd43bf4c [jslib] Update (48144a7) (#81)
* update jslib (48144a7)

* Fixed breaking changes for previous jslib updates
2021-01-05 17:34:18 -06:00
Pasi Niemi
55722d3c04 Add options for giving passwords and secrets as file contents or in an environment variable (#82) 2021-01-04 11:53:11 -05:00
Chad Scharf
77043d8d66 Merge pull request #44 from NitorCreations/filter_by_administrativeunit
Enable filtering Azure AD directory by administrative unit
2020-12-30 18:10:10 -05:00
Pasi Niemi
002117a6e5 Fix as per the comment about naming and visibility conventions 2020-12-30 22:53:49 +02:00
Vincent Salucci
5aa8097cfd Update jslib (abb54f0 -> 72bf18f) (#79) 2020-12-09 09:48:30 -06:00
Kyle Spearrin
19e1049566 use env variable for apple id password 2020-12-03 23:10:30 -05:00
Kyle Spearrin
87bdc88e22 bump version 2020-12-03 23:01:06 -05:00
Chad Scharf
950e3ae91e Merge pull request #78 from bitwarden/update-jslib-sso
jslib update to fix SSO from DC
2020-12-02 14:52:53 -05:00
Chad Scharf
a37532e1ad Merge branch 'master' into update-jslib-sso
merge lastest from master into jslib update branch
2020-12-02 14:27:55 -05:00
Chad Scharf
21ff5f311b Merge pull request #77 from bitwarden/update-jslib
Update jslib
2020-12-02 14:24:30 -05:00
Chad Scharf
7dd12cf0cb jslib update to fix SSO from DC 2020-12-02 14:16:30 -05:00
Chad Scharf
6f8df7a690 Update jslib 2020-11-23 16:08:12 -05:00
Chad Scharf
1ff0ef1f90 Merge pull request #75 from bitwarden/fix-build-error
Revert https-proxy-agent version
2020-11-13 15:07:31 -05:00
Chad Scharf
196fc10d80 Revert https-proxy-agent version 2020-11-13 14:54:36 -05:00
Chad Scharf
7496de4cc6 Merge pull request #74 from kitos9112/master
Bump https-proxy-agent dep. to 5.0.0
2020-11-13 14:09:02 -05:00
Marcos Soutullo Rodriguez
02a6adf6a2 Bump https-proxy-agent dep. to 5.0.0 2020-11-13 18:51:54 +00:00
Pasi Niemi
5848553a4b Merge remote-tracking branch 'upstream/master' into filter_by_administrativeunit 2020-09-24 12:36:32 +03:00
Pasi Niemi
3840bce6d7 Enable filtering Azure AD directory by administrative unit 2020-05-19 08:00:25 +03:00
79 changed files with 26220 additions and 10544 deletions

29
.github/scripts/decrypt-secret.ps1 vendored Normal file
View 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
View 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;

View 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"

View 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

Binary file not shown.

Binary file not shown.

BIN
.github/secrets/macdev-cert.p12.gpg vendored Normal file

Binary file not shown.

414
.github/workflows/build.yml vendored Normal file
View 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
View 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 }}

13
.vscode/launch.json vendored
View File

@@ -23,7 +23,18 @@
"port": 9223,
"webRoot": "${workspaceFolder}/build",
"sourceMaps": true
}
},
{
"type": "node",
"request": "launch",
"name": "Debug CLI",
"protocol": "inspector",
"cwd": "${workspaceFolder}",
"program": "${workspaceFolder}/build-cli/bwdc.js",
"args": [
"sync"
]
}
],
"compounds": [
{

View File

@@ -1,4 +1,4 @@
[![appveyor build](https://ci.appveyor.com/api/projects/status/github/bitwarden/directory-connector?branch=master&svg=true)](https://ci.appveyor.com/project/bitwarden/directory-connector)
![Build](https://github.com/bitwarden/directory-connector/workflows/Build/badge.svg)
[![Join the chat at https://gitter.im/bitwarden/Lobby](https://badges.gitter.im/bitwarden/Lobby.svg)](https://gitter.im/bitwarden/Lobby)
# Bitwarden Directory Connector
@@ -47,7 +47,7 @@ We provide detailed documentation and examples for using the Directory Connector
**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).
**Run the app**

View File

@@ -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

View File

@@ -20,12 +20,6 @@ function webfonts() {
.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.cleanupAotIssue = cleanupAotIssue;
exports.webfonts = gulp.series(clean, webfonts);
exports['prebuild:renderer'] = gulp.parallel(webfonts, cleanupAotIssue);;
exports['prebuild:renderer'] = webfonts;;

2
jslib

Submodule jslib updated: 23ded0d115...c70c8ecc24

33640
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -21,14 +21,14 @@
"sub:update": "git submodule update --remote",
"sub:pull": "git submodule foreach git pull origin master",
"sub:commit": "npm run sub:pull && git commit -am \"update submodule\"",
"postinstall": "npm run sub:init",
"preinstall": "npm run sub:init",
"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": "./node_modules/.bin/electron-rebuild",
"rebuild": "electron-rebuild",
"reset": "rimraf ./node_modules/keytar/* && npm install",
"lint": "tslint src/**/*.ts || true",
"lint:fix": "tslint src/**/*.ts --fix",
"lint": "tslint 'src/**/*.ts' || true",
"lint:fix": "tslint 'src/**/*.ts' --fix",
"build": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\"",
"build:main": "webpack --config webpack.main.js",
"build:renderer": "gulp prebuild:renderer && webpack --config webpack.renderer.js",
@@ -46,9 +46,9 @@
"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 && 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:win": "pkg . --targets win-x64 --output ./dist-cli/windows/bwdc.exe",
"pack:cli:mac": "pkg . --targets macos-x64 --output ./dist-cli/macos/bwdc",
"pack:cli:lin": "pkg . --targets linux-x64 --output ./dist-cli/linux/bwdc",
"pack:cli:win": "pkg ./src-cli --targets win-x64 --output ./dist-cli/windows/bwdc.exe",
"pack:cli:mac": "pkg ./src-cli --targets macos-x64 --output ./dist-cli/macos/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:mac": "npm run build:dist && npm run pack:mac",
"dist:win": "npm run build:dist && npm run pack:win",
@@ -85,7 +85,8 @@
"target": [
"portable",
"nsis"
]
],
"sign": "scripts/sign.js"
},
"linux": {
"category": "Utility",
@@ -130,100 +131,69 @@
"artifactName": "Bitwarden-Connector-${version}-${arch}.${ext}"
}
},
"bin": {
"bwdc": "./build-cli/bwdc.js"
},
"pkg": {
"assets": "./build-cli/**/*"
},
"devDependencies": {
"@angular/compiler-cli": "^9.1.12",
"@angular/compiler-cli": "^11.2.11",
"@microsoft/microsoft-graph-types": "^1.4.0",
"@ngtools/webpack": "^9.1.12",
"@types/commander": "^2.12.2",
"@types/form-data": "^2.2.1",
"@types/inquirer": "^0.0.43",
"@types/ldapjs": "^1.0.3",
"@types/lowdb": "^1.0.5",
"@types/lunr": "^2.3.3",
"@types/node": "^10.17.28",
"@types/node-fetch": "^2.1.2",
"@types/node-forge": "^0.7.5",
"@types/papaparse": "^4.5.3",
"@types/semver": "^5.5.0",
"@types/source-map": "0.5.2",
"@types/webcrypto": "^0.0.28",
"@types/webpack": "^4.4.11",
"@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": "6.1.7",
"electron-builder": "22.4.0",
"electron-notarize": "^0.2.1",
"electron-rebuild": "^1.9.0",
"@ngtools/webpack": "^11.2.10",
"@types/ldapjs": "^1.0.10",
"@types/node": "^14.14.43",
"@types/proper-lockfile": "^4.1.1",
"clean-webpack-plugin": "^3.0.0",
"concurrently": "^6.0.2",
"copy-webpack-plugin": "^6.4.0",
"cross-env": "^7.0.3",
"css-loader": "^5.2.4",
"del": "^6.0.0",
"electron-builder": "^22.10.5",
"electron-notarize": "^1.0.0",
"electron-rebuild": "^2.3.5",
"electron-reload": "^1.5.0",
"mini-css-extract-plugin": "^0.9.0",
"file-loader": "^2.0.0",
"file-loader": "^6.2.0",
"font-awesome": "4.7.0",
"gulp": "^4.0.0",
"gulp-google-webfonts": "^2.0.0",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"node-abi": "^2.9.0",
"node-loader": "^0.6.0",
"node-sass": "^4.13.1",
"pkg": "4.3.4",
"rimraf": "^2.6.2",
"sass-loader": "^7.1.0",
"ts-loader": "^7.0.5",
"tslint": "^5.12.1",
"gulp": "^4.0.2",
"gulp-google-webfonts": "^4.0.0",
"html-loader": "^1.3.2",
"html-webpack-plugin": "^4.5.1",
"mini-css-extract-plugin": "^1.5.0",
"node-loader": "^1.0.3",
"pkg": "^5.1.0",
"rimraf": "^3.0.2",
"sass": "^1.32.11",
"sass-loader": "^10.1.1",
"tapable": "^1.1.3",
"ts-loader": "^8.1.0",
"tsconfig-paths-webpack-plugin": "^3.5.1",
"tslint": "~6.1.0",
"tslint-loader": "^3.5.4",
"typescript": "3.8.3",
"webpack": "^4.29.0",
"webpack-cli": "^3.2.1",
"webpack-merge": "^4.2.1",
"webpack-node-externals": "^1.7.2"
"typescript": "4.1.5",
"webpack": "^4.46.0",
"webpack-cli": "^4.6.0",
"webpack-merge": "^5.7.3",
"webpack-node-externals": "^3.0.0",
"prebuild-install": "^5.0.0"
},
"dependencies": {
"@angular/animations": "9.1.12",
"@angular/common": "9.1.12",
"@angular/compiler": "9.1.12",
"@angular/core": "9.1.12",
"@angular/forms": "9.1.12",
"@angular/platform-browser": "9.1.12",
"@angular/platform-browser-dynamic": "9.1.12",
"@angular/router": "9.1.12",
"@angular/upgrade": "9.1.12",
"@microsoft/microsoft-graph-client": "1.2.0",
"angular2-toaster": "8.0.0",
"angulartics2": "9.1.0",
"big-integer": "1.6.36",
"bootstrap": "4.3.1",
"chalk": "2.4.1",
"commander": "2.18.0",
"core-js": "2.6.2",
"@bitwarden/jslib-angular": "file:jslib/angular",
"@bitwarden/jslib-common": "file:jslib/common",
"@bitwarden/jslib-electron": "file:jslib/electron",
"@bitwarden/jslib-node": "file:jslib/node",
"@microsoft/microsoft-graph-client": "^2.2.1",
"angular2-toaster": "^11.0.1",
"bootstrap": "^4.6.0",
"chalk": "^4.1.1",
"commander": "^7.2.0",
"core-js": "^3.11.0",
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git",
"electron-log": "2.2.17",
"electron-store": "1.3.0",
"electron-updater": "4.2.0",
"form-data": "2.3.2",
"googleapis": "43.0.0",
"https-proxy-agent": "4.0.0",
"inquirer": "6.2.0",
"keytar": "4.13.0",
"form-data": "^4.0.0",
"googleapis": "^73.0.0",
"inquirer": "8.0.0",
"ldapjs": "git+https://git@github.com/kspearrin/node-ldapjs.git",
"lowdb": "1.0.0",
"lunr": "2.3.3",
"node-fetch": "2.2.0",
"node-forge": "0.7.6",
"open": "7.1.0",
"rxjs": "6.6.2",
"tslib": "^2.0.1",
"zone.js": "0.10.3",
"zxcvbn": "4.4.2"
"lunr": "^2.3.9",
"open": "^8.0.6",
"proper-lockfile": "^4.1.2"
},
"engines": {
"node": "~14",
"npm": "~7"
}
}

View File

@@ -6,12 +6,13 @@ exports.default = async function notarizing(context) {
if (electronPlatformName !== 'darwin') {
return;
}
const appleId = process.env.APPLEID;
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: `@keychain:AC_PASSWORD`,
appleIdPassword: appleIdPassword,
});
};

23
scripts/sign.js Normal file
View 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
View 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"
}
}

View File

@@ -8,14 +8,22 @@
<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">
<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="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="password" name="MasterPassword"
[(ngModel)]="masterPassword" class="form-control">
<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">
@@ -25,10 +33,6 @@
<i class="fa fa-sign-in fa-fw" [hidden]="form.loading"></i>
{{'logIn' | i18n}}
</button>
<button type="button" class="btn btn-secondary ml-1" (click)="sso()">
<i class="fa fa-bank" aria-hidden="true"></i>
{{'enterpriseSingleSignOn' | i18n}}
</button>
</div>
<button type="button" class="btn btn-link ml-auto" (click)="settings()">
{{'settings' | i18n}}

View 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();
}
}

View File

@@ -1,10 +1,10 @@
import { Component } from '@angular/core';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.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({
selector: 'app-environment',

View File

@@ -1,57 +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 { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { StateService } from 'jslib/abstractions/state.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, static: true }) environmentModal: ViewContainerRef;
constructor(authService: AuthService, router: Router,
i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
storageService: StorageService, stateService: StateService,
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
passwordGenerationService: PasswordGenerationService, cryptoFunctionService: CryptoFunctionService) {
super(authService, router,
platformUtilsService, i18nService,
stateService, environmentService,
passwordGenerationService, cryptoFunctionService,
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();
});
}
sso() {
return super.launchSsoBrowser('connector', 'bwdc://sso-callback');
}
}

View File

@@ -1,30 +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" *ngIf="!showMasterPassRedirect">
<div class="card-body">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
{{'loading' | i18n}}
</div>
</div>
<div class="card" *ngIf="showMasterPassRedirect">
<h5 class="card-header">{{'setMasterPassword' | i18n}}</h5>
<div class="card-body">
<p class="text-center">{{'setMasterPasswordRedirect' | i18n}}</p>
<hr>
<div class="d-flex">
<button type="button" class="btn btn-primary btn-block btn-submit"
(click)="launchWebVault()">
{{'launchWebVault' | i18n}}
</button>
<button type="button" class="btn btn-secondary btn-block ml-2 mt-0" (click)="logOut()">
{{'logOut' | i18n}}
</button>
</div>
</div>
</div>
</div>
</div>
</form>
</div>

View File

@@ -1,61 +0,0 @@
import { Component } from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ApiService } from 'jslib/abstractions/api.service';
import { AuthService } from 'jslib/abstractions/auth.service';
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { StateService } from 'jslib/abstractions/state.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { SsoComponent as BaseSsoComponent } from 'jslib/angular/components/sso.component';
@Component({
selector: 'app-sso',
templateUrl: 'sso.component.html',
})
export class SsoComponent extends BaseSsoComponent {
showMasterPassRedirect: boolean = false;
constructor(authService: AuthService, router: Router,
i18nService: I18nService, route: ActivatedRoute,
storageService: StorageService, stateService: StateService,
platformUtilsService: PlatformUtilsService, apiService: ApiService,
cryptoFunctionService: CryptoFunctionService,
passwordGenerationService: PasswordGenerationService, private messagingService: MessagingService,
private environmentService: EnvironmentService) {
super(authService, router, i18nService, route, storageService, stateService, platformUtilsService,
apiService, cryptoFunctionService, passwordGenerationService);
this.successRoute = '/tabs/dashboard';
this.redirectUri = 'bwdc://sso-callback';
this.clientId = 'connector';
this.onSuccessfulLoginChangePasswordNavigate = this.redirectSetMasterPass;
}
async redirectSetMasterPass() {
this.showMasterPassRedirect = true;
}
launchWebVault() {
const webUrl = this.environmentService.webVaultUrl == null ? 'https://vault.bitwarden.com' :
this.environmentService.webVaultUrl;
this.platformUtilsService.launchUri(webUrl);
}
async logOut() {
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('logOutConfirmation'),
this.i18nService.t('logOut'), this.i18nService.t('logOut'), this.i18nService.t('cancel'));
if (confirmed) {
this.messagingService.send('logout');
}
}
}

View File

@@ -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">&times;</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>

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -1,64 +0,0 @@
import {
Component,
ComponentFactoryResolver,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import {
ActivatedRoute,
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 { StateService } from 'jslib/abstractions/state.service';
import { StorageService } from 'jslib/abstractions/storage.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, static: true }) twoFactorOptionsModal: ViewContainerRef;
constructor(authService: AuthService, router: Router,
i18nService: I18nService, apiService: ApiService,
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
private componentFactoryResolver: ComponentFactoryResolver, stateService: StateService,
storageService: StorageService, route: ActivatedRoute) {
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService,
stateService, storageService, route);
}
async ngOnInit() {
await super.ngOnInit();
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();
});
}
}

View File

@@ -7,9 +7,7 @@ import {
import { AuthGuardService } from './services/auth-guard.service';
import { LaunchGuardService } from './services/launch-guard.service';
import { LoginComponent } from './accounts/login.component';
import { SsoComponent } from './accounts/sso.component';
import { TwoFactorComponent } from './accounts/two-factor.component';
import { ApiKeyComponent } from './accounts/apiKey.component';
import { DashboardComponent } from './tabs/dashboard.component';
import { MoreComponent } from './tabs/more.component';
import { SettingsComponent } from './tabs/settings.component';
@@ -19,11 +17,9 @@ const routes: Routes = [
{ path: '', redirectTo: '/login', pathMatch: 'full' },
{
path: 'login',
component: LoginComponent,
component: ApiKeyComponent,
canActivate: [LaunchGuardService],
},
{ path: '2fa', component: TwoFactorComponent },
{ path: 'sso', component: SsoComponent },
{
path: 'tabs',
component: TabsComponent,

View File

@@ -5,8 +5,6 @@ import {
ToasterContainerComponent,
ToasterService,
} from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
import {
Component,
@@ -21,18 +19,18 @@ import {
import { DomSanitizer } from '@angular/platform-browser';
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 { AuthService } from 'jslib/abstractions/auth.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { StateService } from 'jslib/abstractions/state.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { TokenService } from 'jslib/abstractions/token.service';
import { UserService } from 'jslib/abstractions/user.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { TokenService } from 'jslib-common/abstractions/token.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { ConfigurationService } from '../services/configuration.service';
import { SyncService } from '../services/sync.service';
@@ -60,10 +58,9 @@ export class AppComponent implements OnInit {
private lastActivity: number = null;
private modal: ModalComponent = null;
constructor(private angulartics2GoogleAnalytics: Angulartics2GoogleAnalytics,
private broadcasterService: BroadcasterService, private userService: UserService,
constructor(private broadcasterService: BroadcasterService, private userService: UserService,
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 sanitizer: DomSanitizer, private ngZone: NgZone,
private componentFactoryResolver: ComponentFactoryResolver, private messagingService: MessagingService,
@@ -76,12 +73,6 @@ export class AppComponent implements OnInit {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(async () => {
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 'syncScheduleStopped':
this.stateService.save('syncingDir', message.command === 'syncScheduleStarted');
@@ -127,12 +118,6 @@ export class AppComponent implements OnInit {
case 'showToast':
this.showToast(message);
break;
case 'analyticsEventTrack':
this.analytics.eventTrack.next({
action: message.action,
properties: { label: message.label },
});
break;
case 'ssoCallback':
this.router.navigate(['sso'], { queryParams: { code: message.code, state: message.state } });
break;
@@ -149,13 +134,10 @@ export class AppComponent implements OnInit {
private async logOut(expired: boolean) {
const userId = await this.userService.getUserId();
await Promise.all([
this.tokenService.clearToken(),
this.userService.clear(),
]);
await this.tokenService.clearToken();
await this.userService.clear();
this.authService.logOut(async () => {
this.analytics.eventTrack.next({ action: 'Logged Out' });
if (expired) {
this.toasterService.popAsync('warning', this.i18nService.t('loggedOut'),
this.i18nService.t('loginExpired'));

View File

@@ -1,9 +1,7 @@
import 'core-js';
import 'core-js/stable';
import 'zone.js/dist/zone';
import { ToasterModule } from 'angular2-toaster';
import { Angulartics2Module } from 'angulartics2';
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
import { AppRoutingModule } from './app-routing.module';
import { ServicesModule } from './services/services.module';
@@ -15,31 +13,28 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AppComponent } from './app.component';
import { CalloutComponent } from 'jslib/angular/components/callout.component';
import { IconComponent } from 'jslib/angular/components/icon.component';
import { ModalComponent } from 'jslib/angular/components/modal.component';
import { CalloutComponent } from 'jslib-angular/components/callout.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 { LoginComponent } from './accounts/login.component';
import { SsoComponent } from './accounts/sso.component';
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component';
import { TwoFactorComponent } from './accounts/two-factor.component';
import { DashboardComponent } from './tabs/dashboard.component';
import { MoreComponent } from './tabs/more.component';
import { SettingsComponent } from './tabs/settings.component';
import { TabsComponent } from './tabs/tabs.component';
import { A11yTitleDirective } from 'jslib/angular/directives/a11y-title.directive';
import { ApiActionDirective } from 'jslib/angular/directives/api-action.directive';
import { AutofocusDirective } from 'jslib/angular/directives/autofocus.directive';
import { BlurClickDirective } from 'jslib/angular/directives/blur-click.directive';
import { BoxRowDirective } from 'jslib/angular/directives/box-row.directive';
import { FallbackSrcDirective } from 'jslib/angular/directives/fallback-src.directive';
import { StopClickDirective } from 'jslib/angular/directives/stop-click.directive';
import { StopPropDirective } from 'jslib/angular/directives/stop-prop.directive';
import { A11yTitleDirective } from 'jslib-angular/directives/a11y-title.directive';
import { ApiActionDirective } from 'jslib-angular/directives/api-action.directive';
import { AutofocusDirective } from 'jslib-angular/directives/autofocus.directive';
import { BlurClickDirective } from 'jslib-angular/directives/blur-click.directive';
import { BoxRowDirective } from 'jslib-angular/directives/box-row.directive';
import { FallbackSrcDirective } from 'jslib-angular/directives/fallback-src.directive';
import { StopClickDirective } from 'jslib-angular/directives/stop-click.directive';
import { StopPropDirective } from 'jslib-angular/directives/stop-prop.directive';
import { I18nPipe } from 'jslib/angular/pipes/i18n.pipe';
import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
import { I18nPipe } from 'jslib-angular/pipes/i18n.pipe';
import { SearchCiphersPipe } from 'jslib-angular/pipes/search-ciphers.pipe';
@NgModule({
imports: [
@@ -48,16 +43,12 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
FormsModule,
AppRoutingModule,
ServicesModule,
Angulartics2Module.forRoot({
pageTracking: {
clearQueryParams: true,
},
}),
ToasterModule.forRoot(),
],
declarations: [
A11yTitleDirective,
ApiActionDirective,
ApiKeyComponent,
AppComponent,
AutofocusDirective,
BlurClickDirective,
@@ -68,22 +59,17 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
FallbackSrcDirective,
I18nPipe,
IconComponent,
LoginComponent,
ModalComponent,
MoreComponent,
SearchCiphersPipe,
SettingsComponent,
SsoComponent,
StopClickDirective,
StopPropDirective,
TabsComponent,
TwoFactorComponent,
TwoFactorOptionsComponent,
],
entryComponents: [
EnvironmentComponent,
ModalComponent,
TwoFactorOptionsComponent,
],
providers: [],
bootstrap: [AppComponent],

View File

@@ -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 {
}

View File

@@ -1,7 +1,7 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { isDev } from 'jslib/electron/utils';
import { isDev } from 'jslib-electron/utils';
// tslint:disable-next-line
require('../scss/styles.scss');

View File

@@ -3,17 +3,17 @@ import {
CanActivate,
Router,
} from '@angular/router';
import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { UserService } from 'jslib/abstractions/user.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
@Injectable()
export class AuthGuardService implements CanActivate {
constructor(private userService: UserService, private router: Router,
constructor(private apiKeyService: ApiKeyService, private router: Router,
private messagingService: MessagingService) { }
async canActivate() {
const isAuthed = await this.userService.isAuthenticated();
const isAuthed = await this.apiKeyService.isAuthenticated();
if (!isAuthed) {
this.messagingService.send('logout');
return false;

View File

@@ -4,14 +4,14 @@ import {
Router,
} from '@angular/router';
import { UserService } from 'jslib/abstractions/user.service';
import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service';
@Injectable()
export class LaunchGuardService implements CanActivate {
constructor(private userService: UserService, private router: Router) { }
constructor(private apiKeyService: ApiKeyService, private router: Router) { }
async canActivate() {
const isAuthed = await this.userService.isAuthenticated();
const isAuthed = await this.apiKeyService.isAuthenticated();
if (!isAuthed) {
return true;
}

View File

@@ -1,5 +1,3 @@
import { remote } from 'electron';
import {
APP_INITIALIZER,
NgModule,
@@ -7,11 +5,11 @@ import {
import { ToasterModule } from 'angular2-toaster';
import { ElectronLogService } from 'jslib/electron/services/electronLog.service';
import { ElectronPlatformUtilsService } from 'jslib/electron/services/electronPlatformUtils.service';
import { ElectronRendererMessagingService } from 'jslib/electron/services/electronRendererMessaging.service';
import { ElectronRendererSecureStorageService } from 'jslib/electron/services/electronRendererSecureStorage.service';
import { ElectronStorageService } from 'jslib/electron/services/electronStorage.service';
import { ElectronLogService } from 'jslib-electron/services/electronLog.service';
import { ElectronPlatformUtilsService } from 'jslib-electron/services/electronPlatformUtils.service';
import { ElectronRendererMessagingService } from 'jslib-electron/services/electronRendererMessaging.service';
import { ElectronRendererSecureStorageService } from 'jslib-electron/services/electronRendererSecureStorage.service';
import { ElectronRendererStorageService } from 'jslib-electron/services/electronRendererStorage.service';
import { AuthGuardService } from './auth-guard.service';
import { LaunchGuardService } from './launch-guard.service';
@@ -20,72 +18,78 @@ import { ConfigurationService } from '../../services/configuration.service';
import { I18nService } from '../../services/i18n.service';
import { SyncService } from '../../services/sync.service';
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
import { ValidationService } from 'jslib/angular/services/validation.service';
import { BroadcasterService } from 'jslib-angular/services/broadcaster.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 { 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 { PasswordGenerationService } from 'jslib/services/passwordGeneration.service';
import { PolicyService } from 'jslib/services/policy.service';
import { StateService } from 'jslib/services/state.service';
import { TokenService } from 'jslib/services/token.service';
import { UserService } from 'jslib/services/user.service';
import { NodeCryptoFunctionService } from 'jslib-node/services/nodeCryptoFunction.service';
import { ApiService as ApiServiceAbstraction } from 'jslib/abstractions/api.service';
import { AppIdService as AppIdServiceAbstraction } from 'jslib/abstractions/appId.service';
import { AuthService as AuthServiceAbstraction } from 'jslib/abstractions/auth.service';
import { CryptoService as CryptoServiceAbstraction } from 'jslib/abstractions/crypto.service';
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib/abstractions/cryptoFunction.service';
import { EnvironmentService as EnvironmentServiceAbstraction } from 'jslib/abstractions/environment.service';
import { I18nService as I18nServiceAbstraction } from 'jslib/abstractions/i18n.service';
import { LogService as LogServiceAbstraction } from 'jslib/abstractions/log.service';
import { MessagingService as MessagingServiceAbstraction } from 'jslib/abstractions/messaging.service';
import { ApiService as ApiServiceAbstraction } from 'jslib-common/abstractions/api.service';
import { ApiKeyService as ApiKeyServiceAbstraction } from 'jslib-common/abstractions/apiKey.service';
import { AuthService as AuthServiceAbstraction } from 'jslib-common/abstractions/auth.service';
import { CryptoService as CryptoServiceAbstraction } from 'jslib-common/abstractions/crypto.service';
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService as EnvironmentServiceAbstraction } from 'jslib-common/abstractions/environment.service';
import { I18nService as I18nServiceAbstraction } from 'jslib-common/abstractions/i18n.service';
import { LogService as LogServiceAbstraction } from 'jslib-common/abstractions/log.service';
import { MessagingService as MessagingServiceAbstraction } from 'jslib-common/abstractions/messaging.service';
import {
PasswordGenerationService as PasswordGenerationServiceAbstraction,
} from 'jslib/abstractions/passwordGeneration.service';
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib/abstractions/platformUtils.service';
import { PolicyService as PolicyServiceAbstraction } from 'jslib/abstractions/policy.service';
import { StateService as StateServiceAbstraction } from 'jslib/abstractions/state.service';
import { StorageService as StorageServiceAbstraction } from 'jslib/abstractions/storage.service';
import { TokenService as TokenServiceAbstraction } from 'jslib/abstractions/token.service';
import { UserService as UserServiceAbstraction } from 'jslib/abstractions/user.service';
} from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib-common/abstractions/platformUtils.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 i18nService = new I18nService(window.navigator.language, './locales');
const stateService = new StateService();
const broadcasterService = new BroadcasterService();
const messagingService = new ElectronRendererMessagingService(broadcasterService);
const storageService: StorageServiceAbstraction = new ElectronStorageService(remote.app.getPath('userData'));
const storageService: StorageServiceAbstraction = new ElectronRendererStorageService();
const platformUtilsService = new ElectronPlatformUtilsService(i18nService, messagingService, false, storageService);
const secureStorageService: StorageServiceAbstraction = new ElectronRendererSecureStorageService();
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 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 }));
const environmentService = new EnvironmentService(apiService, storageService, null);
const userService = new UserService(tokenService, storageService);
const apiKeyService = new ApiKeyService(tokenService, storageService);
const containerService = new ContainerService(cryptoService);
const authService = new AuthService(cryptoService, apiService, userService, tokenService, appIdService,
i18nService, platformUtilsService, messagingService, null, false);
i18nService, platformUtilsService, messagingService, null, logService, apiKeyService, false);
const configurationService = new ConfigurationService(storageService, secureStorageService);
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);
function refreshTokenCallback(): Promise<any> {
return refreshToken(apiKeyService, authService);
}
export function initFactory(): Function {
return async () => {
await environmentService.setUrlsFromStorage();
@@ -98,7 +102,7 @@ export function initFactory(): Function {
let installAction = null;
const installedVersion = await storageService.get<string>(ConstantsService.installedVersionKey);
const currentVersion = platformUtilsService.getApplicationVersion();
const currentVersion = await platformUtilsService.getApplicationVersion();
if (installedVersion == null) {
installAction = 'install';
} else if (installedVersion !== currentVersion) {
@@ -135,6 +139,7 @@ export function initFactory(): Function {
{ provide: PlatformUtilsServiceAbstraction, useValue: platformUtilsService },
{ provide: ApiServiceAbstraction, useValue: apiService },
{ provide: UserServiceAbstraction, useValue: userService },
{ provide: ApiKeyServiceAbstraction, useValue: apiKeyService },
{ provide: MessagingServiceAbstraction, useValue: messagingService },
{ provide: BroadcasterService, useValue: broadcasterService },
{ provide: StorageServiceAbstraction, useValue: storageService },

View File

@@ -14,38 +14,44 @@
<strong *ngIf="syncRunning" class="text-success">{{'running' | i18n}}</strong>
<strong *ngIf="!syncRunning" class="text-danger">{{'stopped' | i18n}}</strong>
</p>
<button #startBtn (click)="start()" [appApiAction]="startPromise" class="btn btn-primary"
[disabled]="startBtn.loading">
<i class="fa fa-play fa-fw" [hidden]="startBtn.loading"></i>
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!startBtn.loading"></i>
{{'startSync' | i18n}}
</button>
<form #startForm [appApiAction]="startPromise" class="d-inline">
<button (click)="start()" class="btn btn-primary"
[disabled]="startForm.loading">
<i class="fa fa-play fa-fw" [hidden]="startForm.loading"></i>
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!startForm.loading"></i>
{{'startSync' | i18n}}
</button>
</form>
<button (click)="stop()" class="btn btn-primary">
<i class="fa fa-stop fa-fw"></i>
{{'stopSync' | i18n}}
</button>
<button #syncBtn (click)="sync()" [appApiAction]="syncPromise" class="btn btn-primary"
[disabled]="syncBtn.loading">
<i class="fa fa-refresh fa-fw" [ngClass]="{'fa-spin': syncBtn.loading}"></i>
{{'syncNow' | i18n}}
</button>
<form #syncForm [appApiAction]="syncPromise" class="d-inline">
<button (click)="sync()" class="btn btn-primary"
[disabled]="syncForm.loading">
<i class="fa fa-refresh fa-fw" [ngClass]="{'fa-spin': syncForm.loading}"></i>
{{'syncNow' | i18n}}
</button>
</form>
</div>
</div>
<div class="card">
<h3 class="card-header">{{'testing' | i18n}}</h3>
<div class="card-body">
<p>{{'testingDesc' | i18n}}</p>
<button #simBtn (click)="simulate()" [appApiAction]="simPromise" class="btn btn-primary"
[disabled]="simBtn.loading">
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!simBtn.loading"></i>
<i class="fa fa-bug fa-fw" [hidden]="simBtn.loading"></i>
{{'testNow' | i18n}}
</button>
<form #simForm [appApiAction]="simPromise" class="d-inline">
<button (click)="simulate()" class="btn btn-primary"
[disabled]="simForm.loading">
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!simForm.loading"></i>
<i class="fa fa-bug fa-fw" [hidden]="simForm.loading"></i>
{{'testNow' | i18n}}
</button>
</form>
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" id="simSinceLast" [(ngModel)]="simSinceLast">
<label class="form-check-label" for="simSinceLast">{{'testLastSync' | i18n}}</label>
</div>
<ng-container *ngIf="!simBtn.loading && (simUsers || simGroups)">
<ng-container *ngIf="!simForm.loading && (simUsers || simGroups)">
<hr />
<div class="row">
<div class="col-lg">

View File

@@ -8,9 +8,9 @@ import {
import { ToasterService } from 'angular2-toaster';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { StateService } from 'jslib/abstractions/state.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from '../../services/sync.service';
@@ -19,7 +19,7 @@ import { SimResult } from '../../models/simResult';
import { UserEntry } from '../../models/userEntry';
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';

View File

@@ -8,11 +8,11 @@ import {
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 { MessagingService } from 'jslib/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ConfigurationService } from '../../services/configuration.service';
@@ -32,7 +32,7 @@ export class MoreComponent implements OnInit {
private toasterService: ToasterService, private broadcasterService: BroadcasterService,
private ngZone: NgZone, private changeDetectorRef: ChangeDetectorRef) { }
ngOnInit() {
async ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
@@ -51,7 +51,7 @@ export class MoreComponent implements OnInit {
});
this.year = new Date().getFullYear().toString();
this.version = this.platformUtilsService.getApplicationVersion();
this.version = await this.platformUtilsService.getApplicationVersion();
}
ngOnDestroy() {

View File

@@ -37,8 +37,7 @@
<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>
<label class="form-check-label" for="pagedSearch">{{'ldapPagedResults' | i18n}}</label>
</div>
</div>
<div class="form-group">
@@ -99,8 +98,8 @@
<div class="form-check">
<input class="form-check-input" type="checkbox" id="certDoNotVerify"
[(ngModel)]="ldap.sslAllowUnauthorized" name="CertDoNoVerify">
<label class="form-check-label"
for="certDoNotVerify">{{'ldapCertDoNotVerify' | i18n}}</label>
<label class="form-check-label" for="certDoNotVerify">{{'ldapCertDoNotVerify' |
i18n}}</label>
</div>
</div>
</div>
@@ -122,8 +121,15 @@
</div>
<div class="form-group">
<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">
<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>
@@ -140,7 +146,15 @@
</div>
<div class="form-group">
<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 [hidden]="directory != directoryType.Okta">
@@ -151,8 +165,15 @@
</div>
<div class="form-group">
<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">
<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">
@@ -163,8 +184,15 @@
</div>
<div class="form-group">
<label for="oneLoginClientSecret">{{'clientSecret' | i18n}}</label>
<input type="text" class="form-control" id="oneLoginClientSecret" name="OneLoginClientSecret"
<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>
@@ -214,17 +242,6 @@
</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 class="col-sm">
<div class="card">
@@ -250,6 +267,13 @@
<label class="form-check-label" for="overwriteExisting">{{'overwriteExisting' | i18n}}</label>
</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]="ldap.ad">
<div class="form-group">
@@ -345,12 +369,14 @@
<div class="form-check">
<input class="form-check-input" type="checkbox" id="syncGroups" [(ngModel)]="sync.groups"
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 [hidden]="!sync.groups">
<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"
[(ngModel)]="sync.groupFilter"></textarea>
<small class="text-muted form-text" *ngIf="directory === directoryType.Ldap">{{'ex' | i18n}}

View File

@@ -6,10 +6,10 @@ import {
OnInit,
} from '@angular/core';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { StateService } from 'jslib/abstractions/state.service';
import { I18nService } from 'jslib-common/abstractions/i18n.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';
@@ -37,9 +37,11 @@ export class SettingsComponent implements OnInit, OnDestroy {
okta = new OktaConfiguration();
oneLogin = new OneLoginConfiguration();
sync = new SyncConfiguration();
organizationId: string;
directoryOptions: any[];
organizationOptions: any[];
showLdapPassword: boolean = false;
showAzureKey: boolean = false;
showOktaKey: boolean = false;
showOneLoginSecret: boolean = false;
constructor(private i18nService: I18nService, private configurationService: ConfigurationService,
private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone,
@@ -55,15 +57,6 @@ export class SettingsComponent implements OnInit, OnDestroy {
}
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.ldap = (await this.configurationService.getDirectory<LdapConfiguration>(DirectoryType.Ldap)) ||
this.ldap;
@@ -87,7 +80,6 @@ export class SettingsComponent implements OnInit, OnDestroy {
if (this.ldap != null && this.ldap.ad) {
this.ldap.pagedSearch = true;
}
await this.configurationService.saveOrganizationId(this.organizationId);
await this.configurationService.saveDirectoryType(this.directory);
await this.configurationService.saveDirectory(DirectoryType.Ldap, this.ldap);
await this.configurationService.saveDirectory(DirectoryType.GSuite, this.gsuite);
@@ -105,7 +97,7 @@ export class SettingsComponent implements OnInit, OnDestroy {
const reader = new FileReader();
reader.readAsText(filePicker.files[0], 'utf-8');
reader.onload = (evt) => {
reader.onload = evt => {
this.ngZone.run(async () => {
try {
const result = JSON.parse((evt.target as FileReader).result as string);
@@ -138,4 +130,24 @@ export class SettingsComponent implements OnInit, OnDestroy {
filePicker.type = 'file';
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();
}
}

View File

@@ -1,34 +1,36 @@
import * as fs from 'fs';
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 { I18nService } from './services/i18n.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 { CliPlatformUtilsService } from 'jslib/cli/services/cliPlatformUtils.service';
import { ConsoleLogService } from 'jslib/cli/services/consoleLog.service';
import { CliPlatformUtilsService } from 'jslib-node/cli/services/cliPlatformUtils.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 { 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 { LowdbStorageService } from 'jslib/services/lowdbStorage.service';
import { NodeApiService } from 'jslib/services/nodeApi.service';
import { NodeCryptoFunctionService } from 'jslib/services/nodeCryptoFunction.service';
import { NoopMessagingService } from 'jslib/services/noopMessaging.service';
import { PasswordGenerationService } from 'jslib/services/passwordGeneration.service';
import { TokenService } from 'jslib/services/token.service';
import { UserService } from 'jslib/services/user.service';
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 { NoopMessagingService } from 'jslib-common/services/noopMessaging.service';
import { PasswordGenerationService } from 'jslib-common/services/passwordGeneration.service';
import { TokenService } from 'jslib-common/services/token.service';
import { UserService } from 'jslib-common/services/user.service';
import { StorageService as StorageServiceAbstraction } from 'jslib/abstractions/storage.service';
import { StorageService as StorageServiceAbstraction } from 'jslib-common/abstractions/storage.service';
import { Program } from './program';
import { refreshToken } from './services/api.service';
// tslint:disable-next-line
const packageJson = require('./package.json');
@@ -47,6 +49,7 @@ export class Main {
appIdService: AppIdService;
apiService: NodeApiService;
environmentService: EnvironmentService;
apiKeyService: ApiKeyService;
userService: UserService;
containerService: ContainerService;
cryptoFunctionService: NodeCryptoFunctionService;
@@ -78,27 +81,29 @@ export class Main {
this.i18nService = new I18nService('en', './locales');
this.platformUtilsService = new CliPlatformUtilsService('connector', packageJson);
this.logService = new ConsoleLogService(this.platformUtilsService.isDev(),
(level) => process.env.BITWARDENCLI_CONNECTOR_DEBUG !== 'true' && level <= LogLevelType.Info);
level => process.env.BITWARDENCLI_CONNECTOR_DEBUG !== 'true' && level <= LogLevelType.Info);
this.cryptoFunctionService = new NodeCryptoFunctionService();
this.storageService = new LowdbStorageService(this.logService, null, this.dataFilePath, true);
this.storageService = new LowdbStorageService(this.logService, null, this.dataFilePath, false, true);
this.secureStorageService = plaintextSecrets ?
this.storageService : new KeytarSecureStorageService(applicationName);
this.cryptoService = new CryptoService(this.storageService, this.secureStorageService,
this.cryptoFunctionService);
this.cryptoFunctionService, this.platformUtilsService, this.logService);
this.appIdService = new AppIdService(this.storageService);
this.tokenService = new TokenService(this.storageService);
this.messagingService = new NoopMessagingService();
this.apiService = new NodeApiService(this.tokenService, this.platformUtilsService,
async (expired: boolean) => await this.logout());
this.environmentService = new EnvironmentService(this.apiService, this.storageService, null);
this.environmentService = new EnvironmentService(this.storageService);
this.apiService = new NodeApiService(this.tokenService, this.platformUtilsService, this.environmentService,
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.containerService = new ContainerService(this.cryptoService);
this.authService = new AuthService(this.cryptoService, this.apiService, this.userService, this.tokenService,
this.appIdService, this.i18nService, this.platformUtilsService, this.messagingService, null, false);
this.appIdService, this.i18nService, this.platformUtilsService, this.messagingService, null,
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.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);
}
@@ -109,14 +114,16 @@ export class Main {
}
async logout() {
await Promise.all([
this.tokenService.clearToken(),
this.userService.clear(),
]);
await this.tokenService.clearToken();
await this.apiKeyService.clear();
}
refreshTokenCallback() {
return refreshToken(this.apiKeyService, this.authService);
}
private async init() {
this.storageService.init();
await this.storageService.init();
this.containerService.attachToWindow(global);
await this.environmentService.setUrlsFromStorage();
// Dev Server URLs. Comment out the line above.
@@ -130,7 +137,7 @@ export class Main {
this.authService.init();
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) {
await this.storageService.save(ConstantsService.installedVersionKey, currentVersion);
}

View File

@@ -1,16 +1,16 @@
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 { Response } from 'jslib/cli/models/response';
import { MessageResponse } from 'jslib/cli/models/response/messageResponse';
import { Response } from 'jslib-node/cli/models/response';
import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse';
export class ClearCacheCommand {
constructor(private configurationService: ConfigurationService, private i18nService: I18nService) { }
async run(cmd: program.Command): Promise<Response> {
async run(cmd: program.OptionValues): Promise<Response> {
try {
await this.configurationService.clearStatefulSettings(true);
const res = new MessageResponse(this.i18nService.t('syncCacheCleared'), null);

View File

@@ -1,14 +1,14 @@
import * as program from 'commander';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { ConfigurationService } from '../services/configuration.service';
import { DirectoryType } from '../enums/directoryType';
import { Response } from 'jslib/cli/models/response';
import { MessageResponse } from 'jslib/cli/models/response/messageResponse';
import { Response } from 'jslib-node/cli/models/response';
import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse';
import { AzureConfiguration } from '../models/azureConfiguration';
import { GSuiteConfiguration } from '../models/gsuiteConfiguration';
@@ -19,6 +19,8 @@ import { SyncConfiguration } from '../models/syncConfiguration';
import { ConnectorUtils } from '../utils';
import { NodeUtils } from 'jslib-common/misc/nodeUtils';
export class ConfigCommand {
private directory: DirectoryType;
private ldap = new LdapConfiguration();
@@ -31,8 +33,15 @@ export class ConfigCommand {
constructor(private environmentService: EnvironmentService, private i18nService: I18nService,
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();
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 {
switch (setting) {
case 'server':

View File

@@ -2,13 +2,13 @@ import * as program from 'commander';
import { ConfigurationService } from '../services/configuration.service';
import { Response } from 'jslib/cli/models/response';
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';
export class LastSyncCommand {
constructor(private configurationService: ConfigurationService) { }
async run(object: string, cmd: program.Command): Promise<Response> {
async run(object: string): Promise<Response> {
try {
switch (object.toLowerCase()) {
case 'groups':

View File

@@ -1,16 +1,14 @@
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 { Response } from 'jslib/cli/models/response';
import { MessageResponse } from 'jslib/cli/models/response/messageResponse';
import { Response } from 'jslib-node/cli/models/response';
import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse';
export class SyncCommand {
constructor(private syncService: SyncService, private i18nService: I18nService) { }
async run(cmd: program.Command): Promise<Response> {
async run(): Promise<Response> {
try {
const result = await this.syncService.sync(false, false);
const groupCount = result[0] != null ? result[0].length : 0;

View File

@@ -1,18 +1,18 @@
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 { ConnectorUtils } from '../utils';
import { Response } from 'jslib/cli/models/response';
import { Response } from 'jslib-node/cli/models/response';
import { TestResponse } from '../models/response/testResponse';
export class TestCommand {
constructor(private syncService: SyncService, private i18nService: I18nService) { }
async run(cmd: program.Command): Promise<Response> {
async run(cmd: program.OptionValues): Promise<Response> {
try {
const result = await ConnectorUtils.simulate(this.syncService, this.i18nService, cmd.last || false);
const res = new TestResponse(result);

View File

@@ -20,12 +20,30 @@
"emailRequired": {
"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": {
"message": "Invalid email address."
},
"masterPassRequired": {
"message": "Master password is required."
},
"missingRequiredInput": {
"message": "Missing required input."
},
"unexpectedError": {
"message": "An unexpected error has occurred."
},
@@ -402,6 +420,12 @@
"groupFilter": {
"message": "Group Filter"
},
"syncGroupsOneLogin": {
"message": "Sync roles"
},
"groupFilterOneLogin": {
"message": "Role Filter"
},
"groupObjectClass": {
"message": "Group Object Class"
},
@@ -417,6 +441,19 @@
"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"
},
@@ -569,7 +606,7 @@
"message": "Welcome to the Bitwarden Directory Connector"
},
"logInDesc": {
"message": "Log in as an organization admin user below."
"message": "Log in with an organization API key below."
},
"dirConfigIncomplete": {
"message": "Directory configuration incomplete."
@@ -603,6 +640,9 @@
}
}
},
"largeImport": {
"message": "More than 2000 users or groups are expected to sync."
},
"overwriteExisting": {
"message": "Overwrite existing organization users based on current sync settings."
},

View File

@@ -5,13 +5,13 @@ import { MenuMain } from './main/menu.main';
import { MessagingMain } from './main/messaging.main';
import { I18nService } from './services/i18n.service';
import { KeytarStorageListener } from 'jslib/electron/keytarStorageListener';
import { ElectronLogService } from 'jslib/electron/services/electronLog.service';
import { ElectronMainMessagingService } from 'jslib/electron/services/electronMainMessaging.service';
import { ElectronStorageService } from 'jslib/electron/services/electronStorage.service';
import { TrayMain } from 'jslib/electron/tray.main';
import { UpdaterMain } from 'jslib/electron/updater.main';
import { WindowMain } from 'jslib/electron/window.main';
import { KeytarStorageListener } from 'jslib-electron/keytarStorageListener';
import { ElectronLogService } from 'jslib-electron/services/electronLog.service';
import { ElectronMainMessagingService } from 'jslib-electron/services/electronMainMessaging.service';
import { ElectronStorageService } from 'jslib-electron/services/electronStorage.service';
import { TrayMain } from 'jslib-electron/tray.main';
import { UpdaterMain } from 'jslib-electron/updater.main';
import { WindowMain } from 'jslib-electron/window.main';
export class Main {
logService: ElectronLogService;
@@ -41,7 +41,7 @@ export class Main {
app.setPath('logs', path.join(app.getPath('userData'), 'logs'));
const args = process.argv.slice(1);
const watch = args.some((val) => val === '--watch');
const watch = args.some(val => val === '--watch');
if (watch) {
// tslint:disable-next-line
@@ -52,8 +52,7 @@ export class Main {
this.i18nService = new I18nService('en', './locales/');
this.storageService = new ElectronStorageService(app.getPath('userData'));
this.windowMain = new WindowMain(this.storageService, false, 800, 600,
(arg) => this.processDeepLink(arg));
this.windowMain = new WindowMain(this.storageService, false, 800, 600, arg => this.processDeepLink(arg), null);
this.menuMain = new MenuMain(this);
this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain, 'directory-connector', () => {
this.messagingService.send('checkingForUpdate');
@@ -64,11 +63,11 @@ export class Main {
}, 'bitwardenDirectoryConnector');
this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.storageService);
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.keytarStorageListener = new KeytarStorageListener('Bitwarden Directory Connector');
this.keytarStorageListener = new KeytarStorageListener('Bitwarden Directory Connector', null);
}
bootstrap() {
@@ -96,7 +95,7 @@ export class Main {
}
private processDeepLink(argv: string[]): void {
argv.filter((s) => s.indexOf('bwdc://') === 0).forEach((s) => {
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');

View File

@@ -6,7 +6,7 @@ import {
import { Main } from '../main';
import { BaseMenu } from 'jslib/electron/baseMenu';
import { BaseMenu } from 'jslib-electron/baseMenu';
export class MenuMain extends BaseMenu {
menu: Menu;

View File

@@ -3,9 +3,9 @@ import {
ipcMain,
} from 'electron';
import { TrayMain } from 'jslib/electron/tray.main';
import { UpdaterMain } from 'jslib/electron/updater.main';
import { WindowMain } from 'jslib/electron/window.main';
import { TrayMain } from 'jslib-electron/tray.main';
import { UpdaterMain } from 'jslib-electron/updater.main';
import { WindowMain } from 'jslib-electron/window.main';
import { MenuMain } from './menu.main';

View File

@@ -3,7 +3,7 @@ import { UserResponse } from './userResponse';
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 {
object: string;
@@ -14,9 +14,9 @@ export class TestResponse implements BaseResponse {
constructor(result: SimResult) {
this.object = 'test';
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.disabledUsers = result.disabledUsers != null ? result.disabledUsers.map((u) => new UserResponse(u)) : [];
this.deletedUsers = result.deletedUsers != null ? result.deletedUsers.map((u) => new UserResponse(u)) : [];
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.disabledUsers = result.disabledUsers != null ? result.disabledUsers.map(u => new UserResponse(u)) : [];
this.deletedUsers = result.deletedUsers != null ? result.deletedUsers.map(u => new UserResponse(u)) : [];
}
}

View File

@@ -6,6 +6,7 @@ export class SyncConfiguration {
groupFilter: string;
removeDisabled = false;
overwriteExisting = false;
largeImport = false;
// Ldap properties
groupObjectClass: string;
userObjectClass: string;

View File

@@ -2,7 +2,7 @@
"name": "bitwarden-directory-connector",
"productName": "Bitwarden Directory Connector",
"description": "Sync your user directory to your Bitwarden organization.",
"version": "2.8.1",
"version": "2.9.4",
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
"homepage": "https://bitwarden.com",
"license": "GPL-3.0",
@@ -12,9 +12,10 @@
"url": "https://github.com/bitwarden/directory-connector"
},
"dependencies": {
"electron-log": "2.2.17",
"electron-store": "1.3.0",
"electron-updater": "4.2.0",
"keytar": "4.13.0"
"browser-hrtime": "^1.1.8",
"electron-log": "4.3.5",
"electron-store": "8.0.0",
"electron-updater": "4.3.9",
"keytar": "7.6.0"
}
}

View File

@@ -1,4 +1,4 @@
import * as chk from 'chalk';
import * as chalk from 'chalk';
import * as program from 'commander';
import * as path from 'path';
@@ -10,16 +10,18 @@ import { LastSyncCommand } from './commands/lastSync.command';
import { SyncCommand } from './commands/sync.command';
import { TestCommand } from './commands/test.command';
import { LoginCommand } from 'jslib/cli/commands/login.command';
import { LogoutCommand } from 'jslib/cli/commands/logout.command';
import { UpdateCommand } from 'jslib/cli/commands/update.command';
import { LoginCommand } from 'jslib-node/cli/commands/login.command';
import { LogoutCommand } from 'jslib-node/cli/commands/logout.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 { StringResponse } from 'jslib/cli/models/response/stringResponse';
import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service';
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 stream = error ? process.stderr : process.stdout;
if (finalLine && process.platform === 'win32') {
@@ -30,18 +32,22 @@ const writeLn = (s: string, finalLine: boolean = false, error: boolean = false)
};
export class Program extends BaseProgram {
private apiKeyService: ApiKeyService;
constructor(private main: Main) {
super(main.userService, writeLn);
this.apiKeyService = main.apiKeyService;
}
run() {
async run() {
program
.option('--pretty', 'Format output. JSON is tabbed with two spaces.')
.option('--raw', 'Return raw output instead of a descriptive message.')
.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('--nointeraction', 'Do not prompt for interactive user input.')
.version(this.main.platformUtilsService.getApplicationVersion(), '-v, --version');
.version(await this.main.platformUtilsService.getApplicationVersion(), '-v, --version');
program.on('option:pretty', () => {
process.env.BW_PRETTY = 'true';
@@ -59,6 +65,10 @@ export class Program extends BaseProgram {
process.env.BW_RESPONSE = 'true';
});
program.on('option:cleanexit', () => {
process.env.BW_CLEANEXIT = 'true';
});
program.on('option:nointeraction', () => {
process.env.BW_NOINTERACTION = 'true';
});
@@ -82,30 +92,26 @@ export class Program extends BaseProgram {
});
program
.command('login [email] [password]')
.description('Log into a user account.')
.option('--method <method>', 'Two-step login method.')
.option('--code <code>', 'Two-step login code.')
.option('--sso', 'Log in with Single-Sign On.')
.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(' bw login --sso');
writeLn('', true);
.command('login [clientId] [clientSecret]')
.description('Log into an organization account.', {
clientId: 'Client_id part of your organization\'s API key',
clientSecret: 'Client_secret part of your organization\'s API key',
})
.action(async (email: string, password: string, cmd: program.Command) => {
.action(async (clientId: string, clientSecret: string, options: program.OptionValues) => {
await this.exitIfAuthed();
const command = new LoginCommand(this.main.authService, this.main.apiService, this.main.i18nService,
this.main.environmentService, this.main.passwordGenerationService, this.main.cryptoFunctionService,
this.main.platformUtilsService, 'connector');
const response = await command.run(email, password, cmd);
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);
});
@@ -115,14 +121,14 @@ export class Program extends BaseProgram {
.on('--help', () => {
writeLn('\n Examples:');
writeLn('');
writeLn(' bw logout');
writeLn(' bwdc logout');
writeLn('', true);
})
.action(async (cmd) => {
.action(async () => {
await this.exitIfNotAuthed();
const command = new LogoutCommand(this.main.authService, this.main.i18nService,
async () => await this.main.logout());
const response = await command.run(cmd);
const response = await command.run();
this.processResponse(response);
});
@@ -137,10 +143,10 @@ export class Program extends BaseProgram {
writeLn(' bwdc test --last');
writeLn('', true);
})
.action(async (cmd) => {
.action(async (options: program.OptionValues) => {
await this.exitIfNotAuthed();
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);
});
@@ -153,10 +159,10 @@ export class Program extends BaseProgram {
writeLn(' bwdc sync');
writeLn('', true);
})
.action(async (cmd) => {
.action(async () => {
await this.exitIfNotAuthed();
const command = new SyncCommand(this.main.syncService, this.main.i18nService);
const response = await command.run(cmd);
const response = await command.run();
this.processResponse(response);
});
@@ -174,16 +180,18 @@ export class Program extends BaseProgram {
writeLn(' bwdc last-sync users');
writeLn('', true);
})
.action(async (object: string, cmd: program.Command) => {
.action(async (object: string) => {
await this.exitIfNotAuthed();
const command = new LastSyncCommand(this.main.configurationService);
const response = await command.run(object, cmd);
const response = await command.run(object);
this.processResponse(response);
});
program
.command('config <setting> <value>')
.command('config <setting> [value]')
.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', () => {
writeLn('\n Settings:');
writeLn('');
@@ -201,16 +209,17 @@ export class Program extends BaseProgram {
writeLn(' bwdc config server bitwarden.com');
writeLn(' bwdc config directory 1');
writeLn(' bwdc config ldap.password <password>');
writeLn(' bwdc config ldap.password --secretenv LDAP_PWD');
writeLn(' bwdc config azure.key <key>');
writeLn(' bwdc config gsuite.key <key>');
writeLn(' bwdc config okta.token <token>');
writeLn(' bwdc config onelogin.secret <secret>');
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,
this.main.configurationService);
const response = await command.run(setting, value, cmd);
const response = await command.run(setting, value, options);
this.processResponse(response);
});
@@ -237,9 +246,9 @@ export class Program extends BaseProgram {
writeLn(' bwdc clear-cache');
writeLn('', true);
})
.action(async (cmd) => {
.action(async (options: program.OptionValues) => {
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);
});
@@ -259,10 +268,10 @@ export class Program extends BaseProgram {
writeLn(' bwdc update --raw');
writeLn('', true);
})
.action(async (cmd) => {
.action(async () => {
const command = new UpdateCommand(this.main.platformUtilsService, this.main.i18nService,
'directory-connector', 'bwdc', false);
const response = await command.run(cmd);
const response = await command.run();
this.processResponse(response);
});
@@ -273,4 +282,20 @@ export class Program extends BaseProgram {
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);
}
}
}

View File

@@ -118,3 +118,27 @@ ul.testing-list {
}
}
.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);
}
}

View 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();
}
}

View 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;
}
}

View File

@@ -12,10 +12,10 @@ import { UserEntry } from '../models/userEntry';
import { BaseDirectoryService } from './baseDirectory.service';
import { ConfigurationService } from './configuration.service';
import { DirectoryService } from './directory.service';
import { IDirectoryService } from './directory.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { LogService } from 'jslib/abstractions/log.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
const NextLink = '@odata.nextLink';
const DeltaLink = '@odata.deltaLink';
@@ -29,7 +29,7 @@ enum UserSetType {
ExcludeGroup,
}
export class AzureDirectoryService extends BaseDirectoryService implements DirectoryService {
export class AzureDirectoryService extends BaseDirectoryService implements IDirectoryService {
private client: graph.Client;
private dirConfig: AzureConfiguration;
private syncConfig: SyncConfiguration;
@@ -68,7 +68,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
let groups: GroupEntry[];
if (this.syncConfig.groups) {
const setFilter = this.createCustomSet(this.syncConfig.groupFilter);
const setFilter = await this.createAadCustomSet(this.syncConfig.groupFilter);
groups = await this.getGroups(setFilter);
users = this.filterUsersFromGroupsSet(users, groups, setFilter, this.syncConfig);
}
@@ -90,7 +90,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
continue;
}
const entry = this.buildUser(user);
if (await this.filterOutUserResult(setFilter, entry)) {
if (await this.filterOutUserResult(setFilter, entry, true)) {
continue;
}
@@ -147,7 +147,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
if (!entry.disabled && !entry.deleted) {
continue;
}
if (await this.filterOutUserResult(setFilter, entry)) {
if (await this.filterOutUserResult(setFilter, entry, false)) {
continue;
}
@@ -170,6 +170,55 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
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>] {
if (filter == null || filter === '') {
return null;
@@ -208,7 +257,8 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
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) {
return false;
}
@@ -224,20 +274,22 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
return this.filterOutResult([userSetTypeExclude, setFilter[1]], user.email);
}
try {
const memberGroups = await this.client.api(`/users/${user.externalId}/checkMemberGroups`).post({
groupIds: Array.from(setFilter[1]),
});
if (memberGroups.value.length > 0 && setFilter[0] === UserSetType.IncludeGroup) {
return false;
} else if (memberGroups.value.length > 0 && setFilter[0] === UserSetType.ExcludeGroup) {
return true;
} else if (memberGroups.value.length === 0 && setFilter[0] === UserSetType.IncludeGroup) {
return true;
} else if (memberGroups.value.length === 0 && setFilter[0] === UserSetType.ExcludeGroup) {
return false;
}
} catch { }
// We need to *not* call the /checkMemberGroups method for deleted users, it will always fail
if (!checkGroupsFilter) {
return false;
}
const memberGroups = await this.client.api(`/users/${user.externalId}/checkMemberGroups`).post({
groupIds: Array.from(setFilter[1]),
});
if (memberGroups.value.length > 0 && setFilter[0] === UserSetType.IncludeGroup) {
return false;
} else if (memberGroups.value.length > 0 && setFilter[0] === UserSetType.ExcludeGroup) {
return true;
} else if (memberGroups.value.length === 0 && setFilter[0] === UserSetType.IncludeGroup) {
return true;
} else if (memberGroups.value.length === 0 && setFilter[0] === UserSetType.ExcludeGroup) {
return false;
}
return false;
}
@@ -331,7 +383,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
private init() {
this.client = graph.Client.init({
authProvider: (done) => {
authProvider: done => {
if (this.dirConfig.applicationId == null || this.dirConfig.key == null ||
this.dirConfig.tenant == null) {
done(this.i18nService.t('dirConfigIncomplete'), null);
@@ -361,7 +413,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(data),
},
}, (res) => {
}, res => {
res.setEncoding('utf8');
res.on('data', (chunk: string) => {
const d = JSON.parse(chunk);
@@ -374,7 +426,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
done('Unknown error (' + res.statusCode + ').', null);
}
});
}).on('error', (err) => {
}).on('error', err => {
done(err, null);
});

View File

@@ -73,7 +73,7 @@ export abstract class BaseDirectoryService {
return users;
}
return users.filter((u) => {
return users.filter(u => {
if (u.deleted) {
return true;
}
@@ -81,11 +81,11 @@ export abstract class BaseDirectoryService {
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 {
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);
}
}

View File

@@ -1,6 +1,6 @@
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 { GSuiteConfiguration } from '../models/gsuiteConfiguration';
import { LdapConfiguration } from '../models/ldapConfiguration';

View File

@@ -1,6 +1,6 @@
import { GroupEntry } from '../models/groupEntry';
import { UserEntry } from '../models/userEntry';
export interface DirectoryService {
export interface IDirectoryService {
getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]>;
}

View File

@@ -13,12 +13,12 @@ import { UserEntry } from '../models/userEntry';
import { BaseDirectoryService } from './baseDirectory.service';
import { ConfigurationService } from './configuration.service';
import { DirectoryService } from './directory.service';
import { IDirectoryService } from './directory.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { LogService } from 'jslib/abstractions/log.service';
import { I18nService } from 'jslib-common/abstractions/i18n.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 service: admin_directory_v1.Admin;
private authParams: any;

View File

@@ -1,7 +1,7 @@
import * as fs from 'fs';
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 {
constructor(systemLanguage: string, localesDirectory: string) {

View File

@@ -4,17 +4,21 @@ import {
setPassword,
} from 'keytar';
import { StorageService } from 'jslib/abstractions/storage.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
export class KeytarSecureStorageService implements StorageService {
constructor(private serviceName: string) { }
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;
});
}
async has(key: string): Promise<boolean> {
return (await this.get(key)) != null;
}
save(key: string, obj: any): Promise<any> {
return setPassword(this.serviceName, key, JSON.stringify(obj));
}

View File

@@ -1,6 +1,8 @@
import * as fs from 'fs';
import * as ldap from 'ldapjs';
import { checkServerIdentity, PeerCertificate } from 'tls';
import { DirectoryType } from '../enums/directoryType';
import { GroupEntry } from '../models/groupEntry';
@@ -9,16 +11,16 @@ import { SyncConfiguration } from '../models/syncConfiguration';
import { UserEntry } from '../models/userEntry';
import { ConfigurationService } from './configuration.service';
import { DirectoryService } from './directory.service';
import { IDirectoryService } from './directory.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { LogService } from 'jslib/abstractions/log.service';
import { I18nService } from 'jslib-common/abstractions/i18n.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;
export class LdapDirectoryService implements DirectoryService {
export class LdapDirectoryService implements IDirectoryService {
private client: ldap.Client;
private dirConfig: LdapConfiguration;
private syncConfig: SyncConfiguration;
@@ -53,7 +55,7 @@ export class LdapDirectoryService implements DirectoryService {
if (this.syncConfig.groups) {
let groupForce = force;
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;
}
groups = await this.getGroups(groupForce);
@@ -303,18 +305,18 @@ export class LdapDirectoryService implements DirectoryService {
return;
}
res.on('error', (resErr) => {
res.on('error', resErr => {
reject(resErr);
});
res.on('searchEntry', (entry) => {
res.on('searchEntry', entry => {
const e = processEntry(entry);
if (e != null) {
entries.push(e);
}
});
res.on('end', (result) => {
res.on('end', result => {
resolve(entries);
});
});
@@ -322,7 +324,7 @@ export class LdapDirectoryService implements DirectoryService {
}
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) {
reject(this.i18nService.t('dirConfigIncomplete'));
return;
@@ -360,9 +362,8 @@ export class LdapDirectoryService implements DirectoryService {
}
}
if (Object.keys(tlsOptions).length > 0) {
options.tlsOptions = tlsOptions;
}
tlsOptions.checkServerIdentity = this.checkServerIdentityAltNames;
options.tlsOptions = tlsOptions;
this.client = ldap.createClient(options);
@@ -381,7 +382,7 @@ export class LdapDirectoryService implements DirectoryService {
if (err != null) {
reject(err.message);
} else {
this.client.bind(user, pass, (err2) => {
this.client.bind(user, pass, err2 => {
if (err2 != null) {
reject(err2.message);
} else {
@@ -391,7 +392,7 @@ export class LdapDirectoryService implements DirectoryService {
}
});
} else {
this.client.bind(user, pass, (err) => {
this.client.bind(user, pass, err => {
if (err != null) {
reject(err.message);
} else {
@@ -402,9 +403,9 @@ export class LdapDirectoryService implements DirectoryService {
});
}
private async unbind(): Promise<any> {
private async unbind(): Promise<void> {
return new Promise((resolve, reject) => {
this.client.unbind((err) => {
this.client.unbind(err => {
if (err != null) {
reject(err);
} else {
@@ -425,4 +426,23 @@ export class LdapDirectoryService implements DirectoryService {
'-' + Utils.fromBufferToHex(p4) + '-' + Utils.fromBufferToHex(p5);
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);
}
}

View 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();
}
}
}

View 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();
}
}

View File

@@ -7,16 +7,19 @@ import { UserEntry } from '../models/userEntry';
import { BaseDirectoryService } from './baseDirectory.service';
import { ConfigurationService } from './configuration.service';
import { DirectoryService } from './directory.service';
import { IDirectoryService } from './directory.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { LogService } from 'jslib/abstractions/log.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import * as https from 'https';
export class OktaDirectoryService extends BaseDirectoryService implements DirectoryService {
const DelayBetweenBuildGroupCallsInMilliseconds = 500;
export class OktaDirectoryService extends BaseDirectoryService implements IDirectoryService {
private dirConfig: OktaConfiguration;
private syncConfig: SyncConfiguration;
private lastBuildGroupCall: number;
constructor(private configurationService: ConfigurationService, private logService: LogService,
private i18nService: I18nService) {
@@ -116,13 +119,11 @@ export class OktaDirectoryService extends BaseDirectoryService implements Direct
this.logService.info('Querying groups.');
await this.apiGetMany('groups?filter=' + this.encodeUrlParameter(oktaFilter)).then(async (groups: any[]) => {
for (const group of groups) {
for (const group of groups.filter(g => !this.filterOutResult(setFilter, g.profile.name))) {
const entry = await this.buildGroup(group);
if (entry != null && !this.filterOutResult(setFilter, entry.name)) {
if (entry != null) {
entries.push(entry);
}
// throttle some to avoid rate limiting
await new Promise((resolve) => setTimeout(resolve, 500));
}
});
return entries;
@@ -134,6 +135,14 @@ export class OktaDirectoryService extends BaseDirectoryService implements Direct
entry.referenceId = group.id;
entry.name = group.profile.name;
// throttle some to avoid rate limiting
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);
@@ -164,7 +173,7 @@ export class OktaDirectoryService extends BaseDirectoryService implements Direct
private async apiGetCall(url: string): Promise<[any, Map<string, string | string[]>]> {
const u = new URL(url);
return new Promise((resolve) => {
return new Promise(resolve => {
https.get({
hostname: u.hostname,
path: u.pathname + u.search,
@@ -173,10 +182,10 @@ export class OktaDirectoryService extends BaseDirectoryService implements Direct
Authorization: 'SSWS ' + this.dirConfig.token,
Accept: 'application/json',
},
}, (res) => {
}, res => {
let body = '';
res.on('data', (chunk) => {
res.on('data', chunk => {
body += chunk;
});

View File

@@ -7,15 +7,15 @@ import { UserEntry } from '../models/userEntry';
import { BaseDirectoryService } from './baseDirectory.service';
import { ConfigurationService } from './configuration.service';
import { DirectoryService } from './directory.service';
import { IDirectoryService } from './directory.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { LogService } from 'jslib/abstractions/log.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 DirectoryService {
export class OneLoginDirectoryService extends BaseDirectoryService implements IDirectoryService {
private dirConfig: OneLoginConfiguration;
private syncConfig: SyncConfiguration;
private accessToken: string;
@@ -72,7 +72,7 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements Di
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) => {
this.allUsers.forEach(user => {
const entry = this.buildUser(user);
if (entry != null && !this.filterOutResult(setFilter, entry.email)) {
entries.push(entry);
@@ -109,7 +109,7 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements Di
const query = this.createDirectoryQuery(this.syncConfig.groupFilter);
this.logService.info('Querying groups.');
const roles = await this.apiGetMany('roles' + (query != null ? '?' + query : ''));
roles.forEach((role) => {
roles.forEach(role => {
const entry = this.buildGroup(role);
if (entry != null && !this.filterOutResult(setFilter, entry.name)) {
entries.push(entry);
@@ -125,7 +125,7 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements Di
entry.name = group.name;
if (this.allUsers != null) {
this.allUsers.forEach((user) => {
this.allUsers.forEach(user => {
if (user.role_id != null && user.role_id.indexOf(entry.referenceId) > -1) {
entry.userMemberExternalIds.add(user.id);
}

View File

@@ -4,21 +4,20 @@ import { GroupEntry } from '../models/groupEntry';
import { SyncConfiguration } from '../models/syncConfiguration';
import { UserEntry } from '../models/userEntry';
import { ImportDirectoryRequest } from 'jslib/models/request/importDirectoryRequest';
import { ImportDirectoryRequestGroup } from 'jslib/models/request/importDirectoryRequestGroup';
import { ImportDirectoryRequestUser } from 'jslib/models/request/importDirectoryRequestUser';
import { OrganizationImportRequest } from 'jslib-common/models/request/organizationImportRequest';
import { ApiService } from 'jslib/abstractions/api.service';
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { LogService } from 'jslib/abstractions/log.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.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 { Utils } from 'jslib/misc/utils';
import { Utils } from 'jslib-common/misc/utils';
import { AzureDirectoryService } from './azure-directory.service';
import { ConfigurationService } from './configuration.service';
import { DirectoryService } from './directory.service';
import { IDirectoryService } from './directory.service';
import { GSuiteDirectoryService } from './gsuite-directory.service';
import { LdapDirectoryService } from './ldap-directory.service';
import { OktaDirectoryService } from './okta-directory.service';
@@ -29,7 +28,8 @@ export class SyncService {
constructor(private configurationService: ConfigurationService, private logService: LogService,
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[]]> {
this.dirType = await this.configurationService.getDirectoryType();
@@ -57,7 +57,16 @@ export class SyncService {
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) {
await this.saveSyncTimes(syncConfig, now);
}
@@ -66,23 +75,29 @@ export class SyncService {
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 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;
const hashBuf = await this.cryptoFunctionService.hash(this.apiService.apiBaseUrl + reqJson, 'sha256');
if (hashBuf != null) {
hash = Utils.fromBufferToB64(hashBuf);
const hashBuff = await this.cryptoFunctionService.hash(this.environmentService.getApiUrl() + orgId + reqJson, 'sha256');
if (hashBuff != null) {
hash = Utils.fromBufferToB64(hashBuff);
}
const lastHash = await this.configurationService.getLastSyncHash();
if (lastHash == null || hash !== lastHash) {
const orgId = await this.configurationService.getOrganizationId();
if (orgId == null) {
throw new Error('Organization not set.');
}
await this.apiService.postImportDirectory(orgId, req);
if (lastHash == null || (hash !== lastHash && hashLegacy !== lastHash)) {
await this.apiService.postPublicImportDirectory(req);
await this.configurationService.saveLastSyncHash(hash);
} else {
groups = null;
@@ -103,8 +118,21 @@ 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 == null || u.email.length <= 50);
return users == null ? null : users.filter(u => u.email?.length <= 256);
}
private flattenUsersToGroups(levelGroups: GroupEntry[], allGroups: GroupEntry[]): Set<string> {
@@ -113,15 +141,15 @@ export class SyncService {
return allUsers;
}
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);
childUsers.forEach((id) => group.userMemberExternalIds.add(id));
childUsers.forEach(id => group.userMemberExternalIds.add(id));
allUsers = new Set([...allUsers, ...group.userMemberExternalIds]);
}
return allUsers;
}
private getDirectoryService(): DirectoryService {
private getDirectoryService(): IDirectoryService {
switch (this.dirType) {
case DirectoryType.GSuite:
return new GSuiteDirectoryService(this.configurationService, this.logService, this.i18nService);
@@ -138,35 +166,26 @@ export class SyncService {
}
}
private buildRequest(groups: GroupEntry[], users: UserEntry[], removeDisabled: boolean,
overwriteExisting: boolean): ImportDirectoryRequest {
const model = new ImportDirectoryRequest();
model.overwriteExisting = overwriteExisting;
if (groups != null) {
for (const g of groups) {
const ig = new ImportDirectoryRequestGroup();
ig.name = g.name;
ig.externalId = g.externalId;
ig.users = Array.from(g.userMemberExternalIds);
model.groups.push(ig);
}
}
if (users != null) {
for (const u of users) {
const iu = new ImportDirectoryRequestUser();
iu.email = u.email;
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 buildRequest(groups: GroupEntry[], users: UserEntry[], removeDisabled: boolean, overwriteExisting: boolean,
largeImport: boolean = false) {
return new OrganizationImportRequest({
groups: (groups ?? []).map(g => {
return {
name: g.name,
externalId: g.externalId,
memberExternalIds: Array.from(g.userMemberExternalIds),
};
}),
users: (users ?? []).map(u => {
return {
email: u.email,
externalId: u.externalId,
deleted: u.deleted || (removeDisabled && u.disabled),
};
}),
overwriteExisting: overwriteExisting,
largeImport: largeImport,
});
}
private async saveSyncTimes(syncConfig: SyncConfiguration, time: Date) {

View File

@@ -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';

View File

@@ -8,58 +8,32 @@
"target": "ES2016",
"allowJs": true,
"sourceMap": true,
"allowSyntheticDefaultImports": true,
"types": [],
"baseUrl": ".",
"paths": {
"tldjs": [
"jslib/src/misc/tldjs.noop"
],
"jslib/*": [
"jslib/src/*"
"jslib-common/*": [
"jslib/common/src/*"
],
"@angular/*": [
"node_modules/@angular/*"
"jslib-angular/*": [
"jslib/angular/src/*"
],
"electron": [
"node_modules/electron"
"jslib-electron/*": [
"jslib/electron/src/*"
],
"node": [
"node_modules/@types/node"
],
"duo_web_sdk": [
"node_modules/duo_web_sdk"
"jslib-node/*": [
"jslib/node/src/*"
]
}
},
"angularCompilerOptions": {
"preserveWhitespaces": true
},
"exclude": [
"node_modules",
"jslib/node_modules",
"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"
"include": [
"src",
"src-cli"
]
}

View File

@@ -49,6 +49,27 @@
"check-separator",
"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"
}
]
}
}

View File

@@ -1,8 +1,9 @@
const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const nodeExternals = require('webpack-node-externals');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
if (process.env.NODE_ENV == null) {
process.env.NODE_ENV = 'development';
@@ -27,12 +28,12 @@ const moduleRules = [
];
const plugins = [
new CleanWebpackPlugin([
path.resolve(__dirname, 'build-cli/*'),
]),
new CopyWebpackPlugin([
{ from: './src/locales', to: 'locales' },
]),
new CleanWebpackPlugin(),
new CopyWebpackPlugin({
patterns: [
{ from: './src/locales', to: 'locales' },
],
}),
new webpack.DefinePlugin({
'process.env.BWCLI_ENV': JSON.stringify(ENV),
}),
@@ -59,12 +60,7 @@ const config = {
},
resolve: {
extensions: ['.ts', '.js', '.json'],
alias: {
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',
},
plugins: [new TsconfigPathsPlugin({ configFile: './tsconfig.json' })],
symlinks: false,
modules: [path.resolve('node_modules')],
},

View File

@@ -1,8 +1,9 @@
const path = require('path');
const merge = require('webpack-merge');
const { merge } = require('webpack-merge');
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 TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const common = {
module: {
@@ -22,10 +23,7 @@ const common = {
plugins: [],
resolve: {
extensions: ['.tsx', '.ts', '.js'],
alias: {
jslib: path.join(__dirname, 'jslib/src'),
tldjs: path.join(__dirname, 'jslib/src/misc/tldjs.noop'),
},
plugins: [new TsconfigPathsPlugin({ configFile: './tsconfig.json' })],
},
output: {
filename: '[name].js',
@@ -55,14 +53,14 @@ const main = {
],
},
plugins: [
new CleanWebpackPlugin([
path.resolve(__dirname, 'build/*'),
]),
new CopyWebpackPlugin([
'./src/package.json',
{ from: './src/images', to: 'images' },
{ from: './src/locales', to: 'locales' },
]),
new CleanWebpackPlugin(),
new CopyWebpackPlugin({
patterns: [
'./src/package.json',
{ from: './src/images', to: 'images' },
{ from: './src/locales', to: 'locales' },
],
}),
],
externals: [nodeExternals()],
};

View File

@@ -1,9 +1,10 @@
const path = require('path');
const webpack = require('webpack');
const merge = require('webpack-merge');
const { merge } = require('webpack-merge');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const AngularCompilerPlugin = require('@ngtools/webpack').AngularCompilerPlugin;
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const common = {
module: {
@@ -33,9 +34,7 @@ const common = {
plugins: [],
resolve: {
extensions: ['.tsx', '.ts', '.js', '.json'],
alias: {
jslib: path.join(__dirname, 'jslib/src'),
},
plugins: [new TsconfigPathsPlugin({ configFile: './tsconfig.json' })],
symlinks: false,
modules: [path.resolve('node_modules')],
},