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

Compare commits

...

286 Commits

Author SHA1 Message Date
github-actions[bot]
6de48441f7 Bumped version to 2.9.9 (#228)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit dc2e17c5db)
2022-02-10 08:55:56 -08:00
Addison Beck
0de0b88aec [chore] Update jslib (#225) 2022-02-07 12:12:37 -05:00
Addison Beck
d519c39761 Update jslib (#222) 2022-02-03 14:48:07 -05:00
Vincent Salucci
a578fb49c7 [Icons] FF - old font cleanup (#221)
* [Icons] Remove FA

* Webpack renderer correction
2022-02-03 10:33:23 -06:00
Addison Beck
1be64836f4 [chore] Update jslib (#217) 2022-01-31 18:01:24 -05:00
Addison Beck
f2389189a3 [chore] Update jslib (#216) 2022-01-28 10:04:08 -05:00
Robyn MacCallum
bb4be6022b Update ldapjs to include fix for EventEmitter issues (#210) 2022-01-27 14:15:48 -05:00
Oscar Hinton
f85a0c5ea5 Fix webpack using double dots (#215) 2022-01-27 19:37:11 +01:00
Vincent Salucci
5afae04b1d [Icons] Update Font Sheet (#203)
* [Icons] Update font sheet

* Updated toaster icon references

* Prettier Updates

* Added import for variable/map references

* Update jslib

* Adding base class to all icon refs

* Removed unused import

* Removed duplicate import

* Update jslib

* Fixed formatting

* Updated eye/eye-slash icon references

* Update jslib

* Update jslib

* Update jslib
2022-01-27 11:10:25 -06:00
Addison Beck
d1b182d20b [bug] Remove redundant state clean call (#214)
* [bug] Remove redundant state clean call

* [refactor] Remove logout override
2022-01-27 08:12:48 -05:00
Addison Beck
9e3d1caee4 [chore] Update jslib & state services to match (#212)
* [chore] Update jslib & state services to match

* [bug] Save userId when migrating state

This is used to check for authentication, so if not present on boot of the app authenticated users will still have to log in again

* [bug] Save added accounts with userId

Currently we are passing in an account object, resulting in a null key. We should be passing in a userId

* [bug] Ensure configs and settings are not cleared on logout

We need to persist directoryConfigruations on logout so that logging out and back in doesn't require folks to need to reconfig their settings

* Remove unneeded LoginSyncService

* Run prettier

* [style] Remove commented lines

Co-authored-by: Thomas Rittson <trittson@bitwarden.com>
2022-01-20 16:31:46 -05:00
Daniel James Smith
9a78956b23 Bump jslib to include electron minor bump (#208)
* Bump jslib to include electron minor bump

* Add changes to package-lock.json
2022-01-20 10:03:07 +01:00
Robyn MacCallum
822655b944 Only get users for includeGroup filter (#205)
* Only get users for includeGroup filter

* Additional refactor after feedback

* refactor building of user entries

* Update src/services/azure-directory.service.ts

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

* Combine user null checks

* Rename variable

* Put deleted users loop back the way it was

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
2022-01-19 15:05:31 -05:00
Oscar Hinton
6dfbe505d9 Fix scoped package name (#209) 2022-01-18 14:04:13 +01:00
Oscar Hinton
0809c2c104 Rename package to @bitwarden/directory-connector (#207) 2022-01-17 17:26:44 +01:00
Vince Grassia
e30000bd00 Update Version Bump action to latest (#206) 2022-01-14 13:31:57 -05:00
Thomas Rittson
90a7601960 Update jslib and minor fixes for account switching (#204)
* Update jslib

* Update stateMigrationService to use enums

* Remove duplicate subclass method

* Update jslib
2022-01-14 05:59:24 +10:00
Daniel James Smith
8a800c6d33 Add ts files to prettier (#202)
* Add ts files to prettier

* Add scss to prettier

* Add all filetypes to prettier and ignore via .prettierignore

* Add --ignore-unknown to prettier
2022-01-07 14:49:42 +01:00
Robyn MacCallum
d0021c9306 Fix date returning as a string (#201) 2022-01-04 09:32:52 -05:00
Daniel James Smith
97673c84da Update year in copyright (#200) 2022-01-04 15:20:55 +01:00
Robyn MacCallum
771a182235 Bug/refresh token fixes (#199)
* Remove NodeApi class extension and use correct apiService in services module

* lint fixes
2021-12-30 15:31:19 -05:00
Robyn MacCallum
857d725a77 override pushAccounts to not delete account from state (#198) 2021-12-29 16:50:45 -05:00
Daniel James Smith
25b3e0f691 Bump electron dependencies (#197) 2021-12-23 12:49:24 +01:00
Addison Beck
d2ba7631b5 [refactor] Implement StateService (#192)
* [refactor(Account Switching)] Implement StateService

* [bug] Migration service updates

* [bug] Fix organizationId coming in as null

* [bug] Use correct storage location

* [bug] Fix secure storage issues

* [bug] Small fixes

* [bug] lint fixes

* [bug] Undo comment

* [bug] Make method names match super

* update jslib

* Add index signature to keys

* Run prettier

* Start dbus

* Start dbus a different way

* Update build.yml

* Add eval

* Init keyring as well

* Remove eval

* Add eval's back

* Remove unused import

* Remove unnecessary null checks

* Change userId to be entityId instead of clientId

* Remove config service

* lint fixes

* Add clientKeys to account

Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com>
2021-12-22 15:16:23 -05:00
Micaiah Martin
a893c78c74 Added logic based on input (#196) 2021-12-20 14:53:30 -07:00
Oscar Hinton
5ff041aa7b Add .git-blame-ignore-revs (#195) 2021-12-20 17:39:59 +01:00
Oscar Hinton
096196fcd5 Apply Prettier (#194) 2021-12-20 17:14:18 +01:00
Oscar Hinton
225073aa33 Add Prettier configuration (#193) 2021-12-20 16:38:40 +01:00
Daniel James Smith
f8b26d82d8 Update build.yml (#189)
* Pull jslib

* Remove install of npm 8
npm 8 is included in node v16
2021-12-13 17:18:01 +01:00
Daniel James Smith
6b98a46b94 Bump node to v16 (#187)
* Pull in jslib

* Bump engines required to node 16 and npm 8

* Bump @types/node to 16
The dep on node 14.18 will get cleaned up once we bump electron

* Modify build.yml to build with node 16 and npm 8

* Update requirements in README.md

* Use pkg-fetch 3.2.5 to retrieve node 16.13.0

* Change pkg-fetch version back to 3.2

* Bump keytar to 7.7.0

* Add missing package-lock.json for src-cli

* Bump keytar to 7.7.0 in src/package.json

* Add missing package-lock.json in src/

* Bump pkg to 5.5.1

* Modify download url for keytar

* Replace Mac and Win keytar download urls
2021-12-10 21:07:59 +01:00
Oscar Hinton
13572b94ee Upgrade Angular to v12 (#184)
Co-authored-by: Daniel James Smith <djsmith@web.de>
2021-12-09 22:30:45 +01:00
Vince Grassia
999b790557 Fix typo (#185) 2021-12-08 10:35:44 -05:00
Oscar Hinton
7c93d59a42 Replace toaster library (#183) 2021-12-07 20:43:57 +01:00
Oscar Hinton
9bec2aa2f0 BEEEP: Refactor services DI (#180) 2021-12-06 12:03:12 +01:00
Robyn MacCallum
240e1d5813 Don't flag deleted users as duplicates (#181)
* Don't flag deleted users as duplicates

* Fix nearby linting error

* Apply user filter to deleted users as well

* Revert "Apply user filter to deleted users as well"

This reverts commit 1633ee265f.

* Only throw error if any duplicates are not deleted

* Rename processedUsers to processedActiveUsers

* Update src/services/sync.service.ts

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

* Update src/services/sync.service.ts

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

* Update src/services/sync.service.ts

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

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
2021-11-30 22:58:40 -05:00
Micaiah Martin
d82f4d90c1 Added version bump workflow (#182) 2021-11-30 15:53:08 -07:00
Daniel James Smith
abc68e8ef9 Add Azure Identity Authority Selector (#176)
* New AzureSettings to select the Identity Authority

* Add fallback for existing customers

* Throw error if Identity Authority is set to invalid value
2021-11-18 19:02:33 +01:00
Daniel James Smith
660ee538ce Update electron to 14.2.0 (#174)
* Pull in jslib and update electron to 14.2.0

* Fix build errors after pulling jslib

* Remove space from tslint ignore comment
2021-11-09 11:38:18 +01:00
Daniel James Smith
a96144d6dc Remove empty catch blocks and remove allow-empty-catch tslint rule (#170)
* Remove empty catch blocks and remove allow-empty-catch tslint rule

* Update jslib to #513

* Fix build errors after update of jslib

* Add missing params to LoginCommand ctor

* Fix build errors due to missing dependencies

* Add changes to package.json and package-lock.json

* Fixed formatting in tslint.json
2021-11-08 12:47:26 +01:00
Vince Grassia
e43d192007 Change release workflow to only allow releases from rc or hotfix branches (#173) 2021-11-05 12:46:03 -04:00
Joseph Flinn
74a018edb8 Version Bump 2.9.8 (#172) 2021-11-04 07:23:10 -07:00
Joseph Flinn
07d0049183 Fix for Linux cli ELF header issue (#171)
* testing new linux build pipeline

* commenting out the check-for-failures job

* fixing syntax error

* fixing the checksum file

* fixing zip archive

* trying the new testing code

* trying to install libsecret-1-0 with sudo

* fixing typo in package version

* fixing the bash testing

* fixing the pwsh to bash conversion

* adding the macos cli job

* switching the keytar release asset name

* reenabling all jobs for final test

* removing the unneeded checksums from the windows cli build

* fixing windows cli build name

* restricting the windows cli build to only building windows
2021-11-04 07:18:37 -07:00
Vince Grassia
5f5358ea0f Add Blockmap artifacts to workflows (#169) 2021-10-29 11:46:29 -04:00
Vince Grassia
36cc6552bf Add support for latest.yml artifacts (#168) 2021-10-27 16:26:03 -04:00
Vince Grassia
05b5fd2eb4 Rename artifact before upload (#167) 2021-10-27 12:37:17 -04:00
Vince Grassia
95f1e86509 Bump version to 2.9.7 (#166) 2021-10-27 11:02:03 -04:00
Thomas Rittson
378dd06274 Add PR template (#165) 2021-10-27 18:59:03 +10:00
Joseph Flinn
314adeb164 Updating the release constraints (#163)
* Updating the release constraints

* removing the master branch release ci code execution

* updating some verbiage
2021-10-22 08:41:09 -07:00
Vince Grassia
cc4f8c9f8d Add notify constraint (#162) 2021-10-15 13:06:39 -04:00
Vince Grassia
35b0e81beb Add Slack alerts for Build workflow failures (#161) 2021-10-14 14:34:09 -04:00
Vince Grassia
9136e3936b Update workflows to fix some minor inconsistencies (#160) 2021-10-08 18:20:37 -04:00
Oscar Hinton
35aead6c0e Bump Electron to v14 (#158) 2021-09-28 16:50:58 +02:00
Vince Grassia
615f3b82db Update pipeline to new model (#159)
* Update workflows to new model
2021-09-28 10:06:42 -04:00
Oscar Hinton
baa441cb90 Use webfonts from jslib instead of downloading them using gulp (#157)
* Use webfonts from jslib instead of downloading them using gulp

* Bump jslib
2021-09-24 12:23:33 -04:00
Vince Grassia
9ad683ca09 Fix variable in versioninfo script (#156) 2021-09-22 14:19:32 -04:00
Joseph Flinn
c2d1d12cd2 CLI release job update (#155)
* updating the cli job in the release workflow to skip the building and use the latest rc build artifacts instead

* switching to downloading all of the artifacts

* renaming download step

* updating the artifact upload paths
2021-09-22 10:58:37 -07:00
Joseph Flinn
3b6bac7668 Version bump to 2.9.6 (#154) 2021-09-22 08:39:09 -07:00
Joseph Flinn
2be879548d Switching the AST install to a custom composite action (#153)
* Switching the AST install to a custom composite action

* fixing linter issues
2021-09-16 10:15:10 -07:00
Thomas Rittson
033c346042 Ignore duplicate users with same externalId (#152)
* Ignore duplicate users with same externalId

* Add null check

* Exclude deleted users from disabled users (Azure)

* Fix order of null check

* Stringify and compare duplicate before discarding
2021-09-14 07:00:37 +10:00
Matt Gibson
32a8e65fe8 Use inline closure to ensure this is defined (#151) 2021-09-03 19:47:01 -05:00
Vince Grassia
b2d4d80181 Update workflows with linter suggestions (#150) 2021-09-02 16:05:01 -04:00
Joseph Flinn
56c1cb23a0 duplicating the fix from the build workflow to the release workflow (#149) 2021-08-26 15:44:25 -07:00
Joseph Flinn
ba26f70d1a Version bump to 2.9.5 (#148) 2021-08-26 08:52:27 -07:00
Joseph Flinn
e5589e7664 Fixes the cli workflow (#147)
* updating build workflow

* fixing the WIN_PKG var

* updating the env var in the makversion script

* fixing spelling error

* fixing the Resource Hacker env vars

* adding in the branch check for macos runner assets
2021-08-25 11:22:30 -07:00
Thomas Rittson
4e82486784 Fix Azure client errors not displaying properly (#144) 2021-08-19 08:32:38 +10:00
Matt Gibson
bb1cdebaf4 Bump patch version to denote no major features released (#145) 2021-08-18 15:40:17 -05:00
Matt Gibson
01405f47c9 Version bump to 2.10.0 (#143) 2021-08-17 14:09:28 -05: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
Chad Scharf
6c95575a8f Merge pull request #73 from bitwarden/version-jslib-update
Version bump + jslib update
2020-10-21 09:46:19 -04:00
Chad Scharf
39755e89a8 Version bump + jslib update 2020-10-21 09:39:54 -04:00
Chad Scharf
c5d3ca218e Merge pull request #71 from bitwarden/fix/bwdc-lint-errors
Fix lint errors/warnings
2020-10-20 10:22:49 -04:00
Chad Scharf
87a2a2a0e4 Merge pull request #72 from bitwarden/add-logging-to-lowdb-storage-svc
Add logger to lowdb storage service
2020-10-20 09:33:38 -04:00
Chad Scharf
5904da2eb1 Add logger to lowdb storage service 2020-10-19 16:05:15 -04:00
Chad Scharf
1ac0c81661 Fix lint errors/warnings 2020-10-19 13:50:08 -04:00
Vincent Salucci
955711714d [SSO] New user provision flow jslib update (5e0a2d1 -> d84d6da) (#70)
* Updated import/constructor

* Update jslib (5e0a2d1 -> d84d6da)
2020-10-14 08:59:40 -05:00
Pasi Niemi
5848553a4b Merge remote-tracking branch 'upstream/master' into filter_by_administrativeunit 2020-09-24 12:36:32 +03:00
Kyle Spearrin
38758caac4 update jslib 2020-09-23 12:02:31 -04:00
Kyle Spearrin
b41a1bdbf4 bump version 2020-09-15 16:38:50 -04:00
Chad Scharf
a400ab7f7d update jslib (#67) 2020-09-15 11:00:20 -04:00
Vincent Salucci
e5d0405882 Added await to prevent race condition for successRoute (#66) 2020-09-10 12:35:52 -05:00
Vincent Salucci
f2137c02f7 Adjusted new success route (#65) 2020-09-09 09:22:54 -05:00
Kyle Spearrin
4c61f465a3 update jslib 2020-09-08 11:24:49 -04:00
Kyle Spearrin
6d22041eab update jslib 2020-09-03 10:35:55 -04:00
Vincent Salucci
752e26db6d [SSO] Adjust set password flow (#64)
* Removed set-password // Adjusted sso UI // Added string

* Removed test logic

* Update jslib (6ab444a -> 700e945)

* Passing null for expectes sync service

* Removing unnecessary async

* Deleted set-password files. RIP

* Removed non-existent import (fixes build)
2020-08-28 09:54:45 -05:00
Vincent Salucci
094ec23f04 Merge pull request #62 from bitwarden/m-set-password
[SSO] Added set password flow
2020-08-24 13:10:30 -05:00
Vincent Salucci
af8ff2901e Fixed theme colors // fixed formatting issues in misc 2020-08-24 11:50:25 -05:00
Vincent Salucci
628689c990 Removed notorization shortcut // Cleaned up extra comma in the service module 2020-08-22 14:43:40 -05:00
Vincent Salucci
ab37221182 Updated UI for password strength // Updated success route // Add more missing strings 2020-08-22 14:28:22 -05:00
Vincent Salucci
626892473f Updated dependency chain // Updated UI // Added Policy Service // Added missing strings // Added callout styles 2020-08-22 12:47:50 -05:00
Vincent Salucci
c621677852 update jslib (5d874d0 -> 6ab444a) 2020-08-22 08:06:08 -05:00
Vincent Salucci
6650b4848d Initial commit for bootstrap styled set-password 2020-08-21 09:24:09 -05:00
Vincent Salucci
c07c56f89b Initial commit of set password flow 2020-08-20 21:32:20 -05:00
Oscar Hinton
294590882f Upgrade to Angular 9 (#60) 2020-08-19 15:49:56 -04:00
Kyle Spearrin
9151b9c2d6 support launching sso from login page button (#59) 2020-08-12 16:03:41 -04:00
Kyle Spearrin
5817468d09 fix login ctor 2020-08-11 14:11:47 -04:00
Kyle Spearrin
31dd20999c update jslib 2020-08-11 12:07:55 -04:00
A Codeweavers Infrastructure Bod
4eb9c9bd4d Fix for #55 (#56)
* Groups that reference all users in an organisation were not being populated

They now are, based on the "customer" member type. The null check for member.status is now not required as the property was only null for groups and now that comparison will not occur.

* If the user is not configured to sync users, but is syncing groups this errored with "users is not iterable"

* Update gsuite-directory.service.ts

Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
2020-08-10 09:39:00 -04:00
Kyle Spearrin
150164534f sso login from bwdc desktop app (#58) 2020-08-05 11:09:06 -04:00
Kyle Spearrin
2b2d8a9fab CLI support for SSO Login (#57)
* sso login support

* fix build and lint issues

* allow web vault URL to be set
2020-08-04 14:19:53 -04:00
Kyle Spearrin
fb122cbbdb fix okta paging (#51)
* fix okta paging

* remove okta package

* use node https instead of a library

* remove bent types

* add 500ms throttle to avoid rate limiter
2020-07-02 14:50:54 -04:00
Kyle Spearrin
0b37857d29 formatting fix 2020-07-02 13:32:08 -04:00
Kyle Spearrin
15c1876687 dont skip disbaled users if not removeDisabled (#50) 2020-07-01 16:15:32 -04:00
Kyle Spearrin
473a6e391d update jslib 2020-06-26 15:23:06 -04:00
Kyle Spearrin
13ad64e6f3 if email field is not valid, try username 2020-06-22 10:43:55 -04:00
Kyle Spearrin
04e278249e add support for email coming from username prop (#47)
* add support for email coming from username prop

* feedback adjustments
2020-06-22 10:28:06 -04:00
Kyle Spearrin
e12c4ea1e2 include all members of the group (#46) 2020-06-16 14:42:38 -04:00
Kyle Spearrin
acfa8632af ignore case 2020-06-04 09:59:45 -04:00
Kyle Spearrin
f53c1f5605 update jslib 2020-05-29 11:10:14 -04:00
Kyle Spearrin
f0f7f89ea8 null checks 2020-05-22 14:28:20 -04:00
Kyle Spearrin
059ff0647a Added option to let user control the paged param (#45) 2020-05-19 12:16:30 -04:00
Kyle Spearrin
d94e5b0620 debug paged = false 2020-05-19 10:27:40 -04:00
Pasi Niemi
3840bce6d7 Enable filtering Azure AD directory by administrative unit 2020-05-19 08:00:25 +03:00
Kyle Spearrin
71cd11eedf update webpack to use MiniCssExtractPlugin 2020-05-08 12:18:03 -04:00
Kyle Spearrin
ecea04bc08 brand color updates 2020-05-05 16:59:26 -04:00
Kyle Spearrin
0575ee5507 skip null user emails 2020-05-04 08:35:39 -04:00
Kyle Spearrin
8b2fa8405b add paging to group members api request 2020-04-24 11:25:00 -04:00
Kyle Spearrin
7d36a50687 update lunr types (#37) 2020-04-14 15:55:39 -04:00
Kyle Spearrin
f7dd9d8d5b filter unsupported user entries by email length 2020-03-27 10:36:41 -04:00
Kyle Spearrin
379b4f4612 update jslib 2020-03-21 00:20:41 -04:00
Kyle Spearrin
4652668e1b Enable OneLogin type from settings 2020-03-13 23:16:19 -04:00
Kyle Spearrin
4e02a8571e UserSelectParams for Azure AD 2020-03-13 23:15:39 -04:00
Kyle Spearrin
bc927a65ac if no dc= in rootPath, just return pathPrefix 2020-03-13 10:12:17 -04:00
Kyle Spearrin
90f1a1c115 noiteration option 2020-03-13 09:06:01 -04:00
Kyle Spearrin
c48acf6038 support onelogin in cli 2020-03-13 09:04:40 -04:00
Kyle Spearrin
2640e8c890 onelogin directory implementation 2020-03-12 22:25:38 -04:00
Kyle Spearrin
094ec55e7f update jslib 2020-03-12 20:27:44 -04:00
Kyle Spearrin
fe39bdac42 remove request interceptor 2020-03-12 14:44:09 -04:00
Kyle Spearrin
2c98d50c43 remove old client ref 2020-03-12 12:53:26 -04:00
Kyle Spearrin
d374cff51c stub out OneLogin connector 2020-03-12 12:42:04 -04:00
Kyle Spearrin
d9e7256804 upgrade electron builder 2020-03-06 11:20:22 -05:00
Kyle Spearrin
0ef2d1523e Revert "update electron builder/updater"
This reverts commit 05bad6f671.
2020-03-06 11:19:13 -05:00
Kyle Spearrin
05bad6f671 update electron builder/updater 2020-03-06 09:24:27 -05:00
Kyle Spearrin
5a62cfcda1 4.2 updater 2020-03-05 10:43:31 -05:00
Kyle Spearrin
634d38510d bump version 2020-03-05 09:32:05 -05:00
Kyle Spearrin
bf27872973 update jslib. tweaks to start tls 2020-03-04 11:31:35 -05:00
Colin Campbell
20bb5a4926 starttls: Support LDAP STARTTLS (#33)
* starttls: Support LDAP STARTTLS

* starttls: Re-roll to preserve old config
2020-03-04 11:08:47 -05:00
Kyle Spearrin
f63fb3ffa0 bitwarden inc. 2020-02-18 22:36:38 -05:00
Kyle Spearrin
f11c32c606 update https proxy 2020-01-27 14:00:29 -05:00
Kyle Spearrin
0b4e22a952 symlink jslib on mac/lin 2020-01-27 12:46:18 -05:00
Kyle Spearrin
a85dbff3a5 upgrade to electron 6 2020-01-27 10:00:21 -05:00
Kyle Spearrin
d2835dd577 remove angualr http and upgrade node-sass 2020-01-27 09:03:41 -05:00
Kyle Spearrin
62abc99b61 devtool false 2020-01-22 14:22:32 -05:00
Kyle Spearrin
20de62cc79 update jslib 2020-01-09 17:30:32 -05:00
Kyle Spearrin
731614279f npm i 2019-10-21 08:45:25 -04:00
Kyle Spearrin
2be751dc8e npm audit fix 2019-10-07 14:57:35 -04:00
Kyle Spearrin
e0409941a3 symlink 2019-09-25 16:10:10 -04:00
Kyle Spearrin
e6a5a3c8c1 update jslib 2019-08-30 14:21:52 -04:00
Kyle Spearrin
3f3590a223 update jslib 2019-08-20 13:47:52 -04:00
Kyle Spearrin
c1f64d7b82 disable-library-validation entitlement 2019-07-31 23:44:11 -04:00
Kyle Spearrin
f90611c96f update keytar 2019-07-31 23:43:34 -04:00
Kyle Spearrin
630e21f7c1 update jslib 2019-07-25 20:43:09 -04:00
Kyle Spearrin
2e81642c0e bump version 2019-07-25 14:21:04 -04:00
Kyle Spearrin
39514b9550 update jslib 2019-07-25 14:18:23 -04:00
Kyle Spearrin
2da82d5610 notarize directory connector 2019-07-25 14:17:50 -04:00
Kyle Spearrin
69f33a08b6 upgrade electron builder 2019-07-24 15:30:43 -04:00
Kyle Spearrin
a05e9c7746 upgrade to electron 5 2019-07-24 14:37:43 -04:00
Kyle Spearrin
d8031e4f49 update jslib 2019-07-10 08:39:21 -04:00
Kyle Spearrin
e6aa07ba5c plaintext secrets env variable 2019-07-05 11:57:25 -04:00
Kyle Spearrin
173129014a re-set state service on log in 2019-07-02 08:37:52 -04:00
Kyle Spearrin
8d4baa6d31 simlink for windows 2019-06-24 21:13:07 -04:00
Kyle Spearrin
20463ce653 update jslib, add node https proxy 2019-06-24 11:09:41 -04:00
Kyle Spearrin
2617f96710 bump version 2019-06-08 13:30:09 -04:00
Kyle Spearrin
53b0614faf only set domain if it has value 2019-06-08 00:15:44 -04:00
Kyle Spearrin
a01db9c448 empty string check on customer 2019-06-08 00:11:29 -04:00
Kyle Spearrin
84dc8e3696 update jslib 2019-06-07 11:22:26 -04:00
Kyle Spearrin
e7b988c042 trimLeft on gsuite key 2019-06-07 11:21:55 -04:00
Kyle Spearrin
a06212af1a write failed responses to stderr 2019-06-04 21:03:30 -04:00
Kyle Spearrin
b217ac9456 update jslib 2019-06-04 00:09:40 -04:00
Kyle Spearrin
ff8422d6c1 alwaysOnTop 2019-06-03 08:41:19 -04:00
Kyle Spearrin
b51d279ba6 menu bar on mac 2019-06-01 22:15:24 -04:00
Kyle Spearrin
78e4601413 update jslib 2019-05-27 10:31:22 -04:00
Kyle Spearrin
5900db10eb update jslib 2019-05-13 08:54:32 -04:00
Kyle Spearrin
c9bd853032 version bump 2019-05-06 21:32:56 -04:00
Kyle Spearrin
976fcad098 overwriteExisting on org user import 2019-05-06 21:32:25 -04:00
Kyle Spearrin
5d8193c348 update jslib 2019-04-18 10:11:38 -04:00
Kyle Spearrin
cc09b26818 hideTitleBar false 2019-04-13 21:37:54 -04:00
Kyle Spearrin
b0247caa4c getDeletedUsers for azure ad 2019-04-12 09:58:19 -04:00
Kyle Spearrin
38316abeae premises 2019-04-04 00:45:46 -04:00
Kyle Spearrin
0991dfa6bd update jslib 2019-04-02 11:54:07 -04:00
Kyle Spearrin
88029663e9 update jslib 2019-04-02 08:20:10 -04:00
Kyle Spearrin
3f5cb10db6 ignore add-edit component 2019-03-29 00:36:07 -04:00
Kyle Spearrin
1c927722f0 update bootstrap 2019-03-29 00:25:06 -04:00
Kyle Spearrin
c04354ba1c npm audit fix 2019-03-29 00:06:07 -04:00
Kyle Spearrin
3335659c8d bump version 2019-03-26 12:24:16 -04:00
Kyle Spearrin
f0282b33f0 add paging to members list api 2019-03-26 09:06:42 -04:00
Kyle Spearrin
f69d461463 move scope of filters for delUsers query 2019-03-26 09:02:35 -04:00
Elie Mélois
bcf16562fe Manage multiple pages when fetching users (#15) 2019-03-26 08:57:01 -04:00
Kyle Spearrin
d7412410f2 group paging cleanup 2019-03-26 08:02:09 -04:00
Elie Mélois
d5907477df Manage multiple pages when fetching groups (#14) 2019-03-26 07:58:05 -04:00
Kyle Spearrin
9acfaff361 remove echo 2019-03-22 22:25:37 -04:00
Kyle Spearrin
737274da59 test for ver info during install 2019-03-22 22:20:41 -04:00
Kyle Spearrin
7a0e0e6915 WIN_PKG set with ps 2019-03-22 22:16:00 -04:00
Kyle Spearrin
25847b9c83 ver info testing 2019-03-22 22:11:32 -04:00
Kyle Spearrin
07dd7bb93e bump version 2019-03-22 17:35:29 -04:00
Kyle Spearrin
bc9936bd92 update lock file 2019-03-22 17:29:38 -04:00
Kyle Spearrin
e4428e3387 update ldapjs fork 2019-03-22 17:21:18 -04:00
Kyle Spearrin
30ef0ce140 back to node 10 2019-03-22 16:55:51 -04:00
Kyle Spearrin
38c461ebe0 use ldapjs fork 2019-03-22 16:55:20 -04:00
Kyle Spearrin
ead1776f90 try node 9 2019-03-22 16:38:49 -04:00
Kyle Spearrin
0e944b2442 simplify sim promise 2019-03-22 13:12:52 -04:00
Kyle Spearrin
3bbd503308 RELEASE_NAME is just numbers 2019-03-20 23:09:54 -04:00
Kyle Spearrin
a7b13b168e release name fix 2019-03-20 23:09:12 -04:00
114 changed files with 27551 additions and 17972 deletions

View File

@@ -7,10 +7,9 @@ root = true
[*]
end_of_line = lf
insert_final_newline = true
quote_type = single
# Set default charset
[*.{js,ts,scss,html}]
charset = utf-8
indent_style = space
indent_size = 4
indent_size = 2

2
.git-blame-ignore-revs Normal file
View File

@@ -0,0 +1,2 @@
# Apply Prettier https://github.com/bitwarden/directory-connector/pull/194
096196fcd512944d1c3d9c007647a1319b032639

33
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,33 @@
## Type of change
- [ ] Bug fix
- [ ] New feature development
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
- [ ] Build/deploy pipeline (DevOps)
- [ ] Other
## Objective
<!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding-->
## Code changes
<!--Explain the changes you've made to each file or major component. This should help the reviewer understand your changes-->
<!--Also refer to any related changes or PRs in other repositories-->
- **file.ext:** Description of what was changed and why
## Screenshots
<!--Required for any UI changes. Delete if not applicable-->
## Testing requirements
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
## Before you submit
- [ ] I have checked for **linting** errors (`npm run lint`) (required)
- [ ] I have added **unit tests** where it makes sense to do so (encouraged but not required)
- [ ] This change requires a **documentation update** (notify the documentation team)
- [ ] This change has particular **deployment requirements** (notify the DevOps team)

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.

688
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,688 @@
---
name: Build
on:
push:
branches-ignore:
- 'l10n_master'
jobs:
cloc:
name: CLOC
runs-on: ubuntu-20.04
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- 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:
name: Setup
runs-on: ubuntu-20.04
outputs:
package_version: ${{ steps.retrieve-version.outputs.package_version }}
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Get Package Version
id: retrieve-version
run: |
PKG_VERSION=$(jq -r .version src/package.json)
echo "::set-output name=package_version::$PKG_VERSION"
linux-cli:
name: Build Linux CLI
runs-on: ubuntu-20.04
needs: setup
env:
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
_PKG_FETCH_NODE_VERSION: 16.13.0
_PKG_FETCH_VERSION: 3.2
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Set up Node
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
node-version: '16'
- name: Update NPM
run: |
npm install -g node-gyp
node-gyp install $(node -v)
- name: Get pkg-fetch
run: |
cd $HOME
fetchedUrl="https://github.com/vercel/pkg-fetch/releases/download/v$_PKG_FETCH_VERSION/node-v$_PKG_FETCH_NODE_VERSION-linux-x64"
mkdir -p .pkg-cache/v$_PKG_FETCH_VERSION
wget $fetchedUrl -O "./.pkg-cache/v$_PKG_FETCH_VERSION/fetched-v$_PKG_FETCH_NODE_VERSION-linux-x64"
- name: Keytar
run: |
keytarVersion=$(cat src/package.json | jq -r '.dependencies.keytar')
keytarTar="keytar-v$keytarVersion-napi-v3-linux-x64.tar"
keytarTarGz="$keytarTar.gz"
keytarUrl="https://github.com/atom/node-keytar/releases/download/v$keytarVersion/$keytarTarGz"
mkdir -p ./keytar/linux
wget $keytarUrl -O ./keytar/linux/$keytarTarGz
tar -xvf ./keytar/linux/$keytarTarGz -C ./keytar/linux
- name: Install
run: npm install
- name: Package CLI
run: npm run dist:cli:lin
- name: Zip
run: |
zip -j ./dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip ./dist-cli/linux/bwdc ./keytar/linux/build/Release/keytar.node
- name: Create checksums
run: |
sha256sum ./dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip | cut -d " " -f 1 > ./dist-cli/bwdc-linux-sha256-$_PACKAGE_VERSION.txt
- name: Version Test
run: |
sudo apt install libsecret-1-0 dbus-x11 gnome-keyring
eval $(dbus-launch --sh-syntax)
eval $(echo -n "" | /usr/bin/gnome-keyring-daemon --login)
eval $(/usr/bin/gnome-keyring-daemon --components=secrets --start)
mkdir -p test/linux
unzip ./dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip -d ./test/linux
testVersion=$(./test/linux/bwdc -v)
echo "version: $_PACKAGE_VERSION"
echo "testVersion: $testVersion"
if [ "$testVersion" != "$_PACKAGE_VERSION" ]; then
echo "Version test failed."
exit 1
fi
- name: Upload Linux Zip to GitHub
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: bwdc-linux-${{ env._PACKAGE_VERSION }}.zip
path: ./dist-cli/bwdc-linux-${{ env._PACKAGE_VERSION }}.zip
if-no-files-found: error
- name: Upload Linux checksum to GitHub
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: bwdc-linux-sha256-${{ env._PACKAGE_VERSION }}.txt
path: ./dist-cli/bwdc-linux-sha256-${{ env._PACKAGE_VERSION }}.txt
if-no-files-found: error
macos-cli:
name: Build Mac CLI
runs-on: macos-11
needs: setup
env:
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
_PKG_FETCH_NODE_VERSION: 16.13.0
_PKG_FETCH_VERSION: 3.2
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Set up Node
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
node-version: '16'
- name: Update NPM
run: |
npm install -g node-gyp
node-gyp install $(node -v)
- name: Get pkg-fetch
run: |
cd $HOME
fetchedUrl="https://github.com/vercel/pkg-fetch/releases/download/v$_PKG_FETCH_VERSION/node-v$_PKG_FETCH_NODE_VERSION-macos-x64"
mkdir -p .pkg-cache/v$_PKG_FETCH_VERSION
wget $fetchedUrl -O "./.pkg-cache/v$_PKG_FETCH_VERSION/fetched-v$_PKG_FETCH_NODE_VERSION-macos-x64"
- name: Keytar
run: |
keytarVersion=$(cat src/package.json | jq -r '.dependencies.keytar')
keytarTar="keytar-v$keytarVersion-napi-v3-darwin-x64.tar"
keytarTarGz="$keytarTar.gz"
keytarUrl="https://github.com/atom/node-keytar/releases/download/v$keytarVersion/$keytarTarGz"
mkdir -p ./keytar/macos
wget $keytarUrl -O ./keytar/macos/$keytarTarGz
tar -xvf ./keytar/macos/$keytarTarGz -C ./keytar/macos
- name: Install
run: npm install
- name: Package CLI
run: npm run dist:cli:mac
- name: Zip
run: |
zip -j ./dist-cli/bwdc-macos-$_PACKAGE_VERSION.zip ./dist-cli/macos/bwdc ./keytar/macos/build/Release/keytar.node
- name: Create checksums
run: |
sha256sum ./dist-cli/bwdc-macos-$_PACKAGE_VERSION.zip | cut -d " " -f 1 > ./dist-cli/bwdc-macos-sha256-$_PACKAGE_VERSION.txt
- name: Version Test
run: |
mkdir -p test/macos
unzip ./dist-cli/bwdc-macos-$_PACKAGE_VERSION.zip -d ./test/macos
testVersion=$(./test/macos/bwdc -v)
echo "version: $_PACKAGE_VERSION"
echo "testVersion: $testVersion"
if [ "$testVersion" != "$_PACKAGE_VERSION" ]; then
echo "Version test failed."
exit 1
fi
- name: Upload Mac Zip to GitHub
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: bwdc-macos-${{ env._PACKAGE_VERSION }}.zip
path: ./dist-cli/bwdc-macos-${{ env._PACKAGE_VERSION }}.zip
if-no-files-found: error
- name: Upload Mac checksum to GitHub
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: bwdc-macos-sha256-${{ env._PACKAGE_VERSION }}.txt
path: ./dist-cli/bwdc-macos-sha256-${{ env._PACKAGE_VERSION }}.txt
if-no-files-found: error
windows-cli:
name: Build Windows CLI
runs-on: windows-2019
needs: setup
env:
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
_WIN_PKG_FETCH_VERSION: 16.13.0
_WIN_PKG_VERSION: 3.2
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- 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: '16'
- name: Update NPM
run: |
npm install -g node-gyp
node-gyp install $(node -v)
- name: Get pkg-fetch
shell: pwsh
run: |
cd $HOME
$fetchedUrl = "https://github.com/vercel/pkg-fetch/releases/download/v$env:_WIN_PKG_VERSION/node-v$env:_WIN_PKG_FETCH_VERSION-win-x64"
New-Item -ItemType directory -Path ./.pkg-cache
New-Item -ItemType directory -Path ./.pkg-cache/v$env:_WIN_PKG_VERSION
Invoke-RestMethod -Uri $fetchedUrl `
-OutFile "./.pkg-cache/v$env:_WIN_PKG_VERSION/fetched-v$env:_WIN_PKG_FETCH_VERSION-win-x64"
- name: Keytar
shell: pwsh
run: |
$keytarVersion = (Get-Content -Raw -Path ./src/package.json | ConvertFrom-Json).dependencies.keytar
$keytarTar = "keytar-v${keytarVersion}-napi-v3-{0}-x64.tar"
$keytarTarGz = "${keytarTar}.gz"
$keytarUrl = "https://github.com/atom/node-keytar/releases/download/v${keytarVersion}/${keytarTarGz}"
New-Item -ItemType directory -Path ./keytar/windows | Out-Null
Invoke-RestMethod -Uri $($keytarUrl -f "win32") -OutFile "./keytar/windows/$($keytarTarGz -f "win32")"
7z e "./keytar/windows/$($keytarTarGz -f "win32")" -o"./keytar/windows"
7z e "./keytar/windows/$($keytarTar -f "win32")" -o"./keytar/windows"
- name: Setup Version Info
shell: pwsh
run: |
$major, $minor, $patch = $env:_PACKAGE_VERSION.split('.')
$versionInfo = @"
1 VERSIONINFO
FILEVERSION $major,$minor,$patch,0
PRODUCTVERSION $major,$minor,$patch,0
FILEOS 0x40004
FILETYPE 0x1
{
BLOCK "StringFileInfo"
{
BLOCK "040904b0"
{
VALUE "CompanyName", "Bitwarden Inc."
VALUE "ProductName", "Bitwarden"
VALUE "FileDescription", "Bitwarden Directory Connector CLI"
VALUE "FileVersion", "$env:_PACKAGE_VERSION"
VALUE "ProductVersion", "$env:_PACKAGE_VERSION"
VALUE "OriginalFilename", "bwdc.exe"
VALUE "InternalName", "bwdc"
VALUE "LegalCopyright", "Copyright Bitwarden Inc."
}
}
BLOCK "VarFileInfo"
{
VALUE "Translation", 0x0409 0x04B0
}
}
"@
$versionInfo | Out-File ./version-info.rc
- name: Resource Hacker
shell: cmd
run: |
set PATH=%PATH%;C:\Program Files (x86)\Resource Hacker
set WIN_PKG=C:\Users\runneradmin\.pkg-cache\v%_WIN_PKG_VERSION%\fetched-v%_WIN_PKG_FETCH_VERSION%-win-x64
set WIN_PKG_BUILT=C:\Users\runneradmin\.pkg-cache\v%_WIN_PKG_VERSION%\built-v%_WIN_PKG_FETCH_VERSION%-win-x64
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:win
- name: Zip
shell: cmd
run: |
7z a ./dist-cli/bwdc-windows-%_PACKAGE_VERSION%.zip ./dist-cli/windows/bwdc.exe ./keytar/windows/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
- name: Upload Windows Zip to GitHub
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: bwdc-windows-${{ env._PACKAGE_VERSION }}.zip
path: ./dist-cli/bwdc-windows-${{ env._PACKAGE_VERSION }}.zip
if-no-files-found: error
- name: Upload Windows checksum to GitHub
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: bwdc-windows-sha256-${{ env._PACKAGE_VERSION }}.txt
path: ./dist-cli/bwdc-windows-sha256-${{ env._PACKAGE_VERSION }}.txt
if-no-files-found: error
windows-gui:
name: Build Windows GUI
runs-on: windows-2019
needs: setup
env:
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
steps:
- name: Set up .NET
uses: actions/setup-dotnet@a71d1eb2c86af85faa8c772c03fb365e377e45ea
with:
dotnet-version: "3.1.x"
- name: Set up Node
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
node-version: '16'
- name: Update NPM
run: |
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
uses: bitwarden/gh-actions/install-ast@f135c42c8596cb535c5bcb7523c0b2eef89709ac
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- 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: Upload Portable Executable to GitHub
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-Connector-Portable-${{ env._PACKAGE_VERSION }}.exe
path: ./dist/Bitwarden-Connector-Portable-${{ env._PACKAGE_VERSION }}.exe
if-no-files-found: error
- name: Upload Installer Executable to GitHub
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe
path: ./dist/Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe
if-no-files-found: error
- name: Upload Installer Executable Blockmap to GitHub
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe.blockmap
path: ./dist/Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe.blockmap
if-no-files-found: error
- name: Upload latest auto-update artifact
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: latest.yml
path: ./dist/latest.yml
if-no-files-found: error
linux-gui:
name: Build Linux GUI
runs-on: ubuntu-20.04
needs: setup
env:
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
steps:
- name: Set up Node
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
node-version: '16'
- name: Update NPM
run: |
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 # v2.3.4
- name: NPM Install
run: npm install
- name: NPM Rebuild
run: npm run rebuild
- name: NPM Package
run: npm run dist:lin
- name: Upload AppImage
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-x86_64.AppImage
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-x86_64.AppImage
if-no-files-found: error
- name: Upload latest auto-update artifact
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: latest-linux.yml
path: ./dist/latest-linux.yml
if-no-files-found: error
macos-gui:
name: Build MacOS GUI
runs-on: macos-11
needs: setup
env:
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
steps:
- name: Set up Node
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
node-version: '16'
- name: Update NPM
run: |
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
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
shell: bash
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Decrypt secrets
env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
shell: bash
run: |
mkdir -p $HOME/secrets
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output "$HOME/secrets/devid-app-cert.p12" \
"$GITHUB_WORKSPACE/.github/secrets/devid-app-cert.p12.gpg"
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output "$HOME/secrets/devid-installer-cert.p12" \
"$GITHUB_WORKSPACE/.github/secrets/devid-installer-cert.p12.gpg"
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output "$HOME/secrets/macdev-cert.p12" \
"$GITHUB_WORKSPACE/.github/secrets/macdev-cert.p12.gpg"
- name: Set up keychain
env:
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
DEVID_CERT_PASSWORD: ${{ secrets.DEVID_CERT_PASSWORD }}
MACDEV_CERT_PASSWORD: ${{ secrets.MACDEV_CERT_PASSWORD }}
shell: bash
run: |
security create-keychain -p $KEYCHAIN_PASSWORD build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain
security set-keychain-settings -lut 1200 build.keychain
security import "$HOME/secrets/devid-app-cert.p12" -k build.keychain -P $DEVID_CERT_PASSWORD \
-T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
security import "$HOME/secrets/devid-installer-cert.p12" -k build.keychain -P $DEVID_CERT_PASSWORD \
-T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
security import "$HOME/secrets/macdev-cert.p12" -k build.keychain -P $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 $KEYCHAIN_PASSWORD build.keychain
- name: Load package version
run: |
$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;
shell: pwsh
- name: Install Node dependencies
run: npm install
# - name: Run linter
# run: npm run lint
- name: Build application
run: npm run dist:mac
env:
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
- name: Rename Zip Artifact
run: |
cd dist
mv "Bitwarden Directory Connector-${{ env._PACKAGE_VERSION }}-mac.zip" \
"Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-mac.zip"
- name: Upload .zip artifact
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-mac.zip
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-mac.zip
if-no-files-found: error
- name: Upload .dmg artifact
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg
if-no-files-found: error
- name: Upload .dmg Blockmap artifact
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg.blockmap
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg.blockmap
if-no-files-found: error
- name: Upload latest auto-update artifact
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
with:
name: latest-mac.yml
path: ./dist/latest-mac.yml
if-no-files-found: error
check-failures:
name: Check for failures
runs-on: ubuntu-20.04
needs:
- cloc
- setup
- linux-cli
- macos-cli
- windows-cli
- windows-gui
- linux-gui
- macos-gui
steps:
- name: Check if any job failed
if: ${{ (github.ref == 'refs/heads/master') || (github.ref == 'refs/heads/rc') }}
env:
CLOC_STATUS: ${{ needs.cloc.result }}
SETUP_STATUS: ${{ needs.setup.result }}
LINUX_CLI_STATUS: ${{ needs.linux-cli.result }}
MACOS_CLI_STATUS: ${{ needs.macos-cli.result }}
WINDOWS_CLI_STATUS: ${{ needs.windows-cli.result }}
WINDOWS_GUI_STATUS: ${{ needs.windows-gui.result }}
LINUX_GUI_STATUS: ${{ needs.linux-gui.result }}
MACOS_GUI_STATUS: ${{ needs.macos-gui.result }}
run: |
if [ "$CLOC_STATUS" = "failure" ]; then
exit 1
elif [ "$SETUP_STATUS" = "failure" ]; then
exit 1
elif [ "$LINUX_CLI_STATUS" = "failure" ]; then
exit 1
elif [ "$MACOS_CLI_STATUS" = "failure" ]; then
exit 1
elif [ "$WINDOWS_CLI_STATUS" = "failure" ]; then
exit 1
elif [ "$WINDOWS_GUI_STATUS" = "failure" ]; then
exit 1
elif [ "$LINUX_GUI_STATUS" = "failure" ]; then
exit 1
elif [ "$MACOS_GUI_STATUS" = "failure" ]; then
exit 1
fi
- name: Login to Azure - Prod Subscription
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
if: failure()
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
if: failure()
with:
keyvault: "bitwarden-prod-kv"
secrets: "devops-alerts-slack-webhook-url"
- name: Notify Slack on failure
uses: act10ns/slack@e4e71685b9b239384b0f676a63c32367f59c2522 # v1.2.2
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}
with:
status: ${{ job.status }}

94
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,94 @@
---
name: Release
on:
workflow_dispatch:
inputs:
release_type:
description: 'Release Options'
required: true
default: 'Initial Release'
type: choice
options:
- Initial Release
- Redeploy
jobs:
setup:
name: Setup
runs-on: ubuntu-20.04
steps:
- name: Branch check
run: |
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix" ]]; then
echo "==================================="
echo "[!] Can only release from the 'rc' or 'hotfix' branches"
echo "==================================="
exit 1
fi
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Retrieve Directory Connector release version
id: retrieve-version
run: |
PKG_VERSION=$(jq -r .version src/package.json)
echo "::set-output name=package_version::$PKG_VERSION"
- name: Check to make sure Mobile release version has been bumped
if: ${{ github.event.inputs.release_type == 'Initial Release' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
latest_ver=$(hub release -L 1 -f '%T')
latest_ver=${latest_ver:1}
echo "Latest version: $latest_ver"
ver=${{ steps.retrieve-version.outputs.package_version }}
echo "Version: $ver"
if [ "$latest_ver" = "$ver" ]; then
echo "Version has not been bumped!"
exit 1
fi
shell: bash
- name: Get branch name
id: branch
run: |
BRANCH_NAME=$(basename ${{ github.ref }})
echo "::set-output name=branch-name::$BRANCH_NAME"
- name: Download all artifacts
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
with:
workflow: build.yml
workflow_conclusion: success
branch: ${{ steps.branch.outputs.branch-name }}
- name: Create release
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09 # v2.8.5
env:
PKG_VERSION: ${{ steps.retrieve-version.outputs.package_version }}
with:
artifacts: "./bwdc-windows-${{ env.PKG_VERSION }}.zip,
./bwdc-macos-${{ env.PKG_VERSION }}.zip,
./bwdc-linux-${{ env.PKG_VERSION }}.zip,
./bwdc-windows-sha256-${{ env.PKG_VERSION }}.txt,
./bwdc-macos-sha256-${{ env.PKG_VERSION }}.txt,
./bwdc-linux-sha256-${{ env.PKG_VERSION }}.txt,
./Bitwarden-Connector-Portable-${{ env.PKG_VERSION }}.exe,
./Bitwarden-Connector-Installer-${{ env.PKG_VERSION }}.exe,
./Bitwarden-Connector-Installer-${{ env.PKG_VERSION }}.exe.blockmap,
./Bitwarden-Connector-${{ env.PKG_VERSION }}-x86_64.AppImage,
./Bitwarden-Connector-${{ env.PKG_VERSION }}-mac.zip,
./Bitwarden-Connector-${{ env.PKG_VERSION }}.dmg,
./Bitwarden-Connector-${{ env.PKG_VERSION }}.dmg.blockmap,
./latest-linux.yml,
./latest-mac.yml,
./latest.yml"
commit: ${{ github.sha }}
tag: v${{ env.PKG_VERSION }}
name: Version ${{ env.PKG_VERSION }}
body: "<insert release notes here>"
token: ${{ secrets.GITHUB_TOKEN }}
draft: true

65
.github/workflows/version-bump.yml vendored Normal file
View File

@@ -0,0 +1,65 @@
---
name: Version Bump
on:
workflow_dispatch:
inputs:
version_number:
description: "New Version"
required: true
jobs:
bump_version:
name: "Create version_bump_${{ github.event.inputs.version_number }} branch"
runs-on: ubuntu-20.04
steps:
- name: Checkout Branch
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
- name: Create Version Branch
run: |
git switch -c version_bump_${{ github.event.inputs.version_number }}
git push -u origin version_bump_${{ github.event.inputs.version_number }}
- name: Checkout Version Branch
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
with:
ref: version_bump_${{ github.event.inputs.version_number }}
- name: Bump Version - Package
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
with:
version: ${{ github.event.inputs.version_number }}
file_path: "./src/package.json"
- name: Commit files
run: |
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git commit -m "Bumped version to ${{ github.event.inputs.version_number }}" -a
- name: Push changes
run: git push -u origin version_bump_${{ github.event.inputs.version_number }}
- name: Create Version PR
env:
PR_BRANCH: "version_bump_${{ github.event.inputs.version_number }}"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
BASE_BRANCH: master
TITLE: "Bump version to ${{ github.event.inputs.version_number }}"
run: |
gh pr create --title "$TITLE" \
--base "$BASE" \
--head "$PR_BRANCH" \
--label "version update" \
--label "automated pr" \
--body "
## Type of change
- [ ] Bug fix
- [ ] New feature development
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
- [ ] Build/deploy pipeline (DevOps)
- [X] Other
## Objective
Automated version bump to ${{ github.event.inputs.version_number }}"

1
.gitignore vendored
View File

@@ -14,3 +14,4 @@ yarn-error.log
.DS_Store
*.nupkg
*.provisionprofile
*.env

1
.husky/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
_

4
.husky/pre-commit Normal file
View File

@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged

11
.prettierignore Normal file
View File

@@ -0,0 +1,11 @@
# Build directories
build
dist
jslib
# External libraries / auto synced locales
src/locales
# Github Workflows
.github/workflows

3
.prettierrc.json Normal file
View File

@@ -0,0 +1,3 @@
{
"printWidth": 100
}

61
.vscode/launch.json vendored
View File

@@ -1,37 +1,40 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Electron: Main",
"protocol": "inspector",
"cwd": "${workspaceRoot}/build",
"runtimeArgs": [
"--remote-debugging-port=9223",
"."
],
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
},
"sourceMaps": true
{
"type": "node",
"request": "launch",
"name": "Electron: Main",
"protocol": "inspector",
"cwd": "${workspaceRoot}/build",
"runtimeArgs": ["--remote-debugging-port=9223", "."],
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
},
{
"name": "Electron: Renderer",
"type": "chrome",
"request": "attach",
"port": 9223,
"webRoot": "${workspaceFolder}/build",
"sourceMaps": true
}
"sourceMaps": true
},
{
"name": "Electron: Renderer",
"type": "chrome",
"request": "attach",
"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": [
{
"name": "Electron: All",
"configurations": [
"Electron: Main",
"Electron: Renderer"
]
}
{
"name": "Electron: All",
"configurations": ["Electron: Main", "Electron: Renderer"]
}
]
}

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
@@ -6,6 +6,7 @@
The Bitwarden Directory Connector is a a desktop application used to sync your Bitwarden enterprise organization to an existing directory of users and groups.
Supported directories:
- Active Directory
- Any other LDAP-based directory
- Azure Active Directory
@@ -47,7 +48,7 @@ We provide detailed documentation and examples for using the Directory Connector
**Requirements**
- [Node.js](https://nodejs.org/)
- [Node.js](https://nodejs.org) v16.13.1 (LTS)
- 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**
@@ -78,3 +79,23 @@ node ./build-cli/bwdc.js --help
Code contributions are welcome! Please commit any pull requests against the `master` branch. Learn more about how to contribute by reading the [`CONTRIBUTING.md`](CONTRIBUTING.md) file.
Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file.
### Prettier
We recently migrated to using Prettier as code formatter. All previous branches will need to updated to avoid large merge conflicts using the following steps:
1. Check out your local Branch
2. Run `git merge 225073aa335d33ad905877b68336a9288e89ea10`
3. Resolve any merge conflicts, commit.
4. Run `npm run prettier`
5. Commit
6. Run `git merge -Xours 096196fcd512944d1c3d9c007647a1319b032639`
7. Push
#### Git blame
We also recommend that you configure git to ignore the prettier revision using:
```bash
git config blame.ignoreRevsFile .git-blame-ignore-revs
```

View File

@@ -7,7 +7,7 @@ notify us. We welcome working with you to resolve the issue promptly. Thanks in
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every
effort to quickly resolve the issue.
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a
third-party. We may publicly disclose the issue before resolving it, if appropriate.
third-party. We may publicly disclose the issue before resolving it, if appropriate.
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or
degradation of our service. Only interact with accounts you own or with explicit permission of the
account holder.

View File

@@ -1,160 +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(Test-Path -Path $env:WIN_PKG) {
$env:VER_INFO = "true"
}
}
install:
- ps: |
$env:PACKAGE_VERSION = (Get-Content -Raw -Path .\src\package.json | ConvertFrom-Json).version
$env:RELEASE_NAME = "Version ${tagName}"
$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) {
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 -> appveyor.yml'
- '%LOCALAPPDATA%\electron-builder -> appveyor.yml'
- 'C:\Users\appveyor\.pkg-cache\ -> package.json'
-
matrix:
only:
- image: Ubuntu1804
cache:
- '/home/appveyor/.cache/electron -> appveyor.yml'
- '/home/appveyor/.cache/electron-builder -> appveyor.yml'
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

@@ -1,31 +0,0 @@
const gulp = require('gulp');
const googleWebFonts = require('gulp-google-webfonts');
const del = require('del');
const paths = {
cssDir: './src/css/',
};
function clean() {
return del([paths.cssDir]);
}
function webfonts() {
return gulp.src('./webfonts.list')
.pipe(googleWebFonts({
fontsDir: 'webfonts',
cssFilename: 'webfonts.css',
format: 'woff',
}))
.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);;

2
jslib

Submodule jslib updated: 50e6f24679...e0cc754d6f

View File

@@ -1,33 +0,0 @@
$major,$minor,$patch = $env:PACKAGE_VERSION.split('.')
$versionInfo = @"
1 VERSIONINFO
FILEVERSION $major,$minor,$patch,0
PRODUCTVERSION $major,$minor,$patch,0
FILEOS 0x40004
FILETYPE 0x1
{
BLOCK "StringFileInfo"
{
BLOCK "040904b0"
{
VALUE "CompanyName", "8bit Solutions LLC"
VALUE "ProductName", "Bitwarden"
VALUE "FileDescription", "Bitwarden Directory Connector CLI"
VALUE "FileVersion", "$env:PACKAGE_VERSION"
VALUE "ProductVersion", "$env:PACKAGE_VERSION"
VALUE "OriginalFilename", "bwdc.exe"
VALUE "InternalName", "bwdc"
VALUE "LegalCopyright", "Copyright 8bit Solutions LLC"
}
}
BLOCK "VarFileInfo"
{
VALUE "Translation", 0x0409 0x04B0
}
}
"@
$versionInfo | Out-File ./version-info.rc

29894
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,5 @@
{
"name": "bitwarden-directory-connector",
"productName": "Bitwarden Directory Connector",
"name": "@bitwarden/directory-connector",
"description": "Sync your user directory to your Bitwarden organization.",
"version": "0.0.0",
"keywords": [
@@ -9,7 +8,7 @@
"vault",
"password manager"
],
"author": "8bit Solutions LLC <hello@bitwarden.com> (https://bitwarden.com)",
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
"homepage": "https://bitwarden.com",
"repository": {
"type": "git",
@@ -21,15 +20,18 @@
"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",
"rebuild": "./node_modules/.bin/electron-rebuild",
"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": "electron-rebuild",
"reset": "rimraf ./node_modules/keytar/* && npm install",
"lint": "tslint src/**/*.ts || true",
"lint:fix": "tslint src/**/*.ts --fix",
"lint": "tslint 'src/**/*.ts' && prettier --check .",
"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",
"build:renderer:watch": "gulp prebuild:renderer && webpack --config webpack.renderer.js --watch",
"build:renderer": "webpack --config webpack.renderer.js",
"build:renderer:watch": "webpack --config webpack.renderer.js --watch",
"build:dist": "npm run reset && npm run rebuild && npm run build",
"build:cli": "webpack --config webpack.cli.js",
"build:cli:watch": "webpack --config webpack.cli.js --watch",
@@ -38,14 +40,14 @@
"electron": "npm run build:main && concurrently -k -n Main,Rend -c yellow,cyan \"electron --inspect=5858 ./build --watch\" \"npm run build:renderer:watch\"",
"clean:dist": "rimraf ./dist/*",
"clean:dist:cli": "rimraf ./dist-cli/*",
"pack:lin": "npm run clean:dist && build --linux --x64 -p never",
"pack:mac": "npm run clean:dist && build --mac -p never",
"pack:win": "npm run clean:dist && build --win --x64 --ia32 -p never -c.win.certificateSubjectName=\"8bit Solutions LLC\"",
"pack:win:ci": "npm run clean:dist && build --win --x64 --ia32 -p never",
"pack:lin": "npm run clean:dist && electron-builder --linux --x64 -p never",
"pack:mac": "npm run clean:dist && electron-builder --mac -p never",
"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",
@@ -54,20 +56,31 @@
"dist:cli:win": "npm run build:cli:prod && npm run clean:dist:cli && npm run pack:cli:win",
"dist:cli:mac": "npm run build:cli:prod && npm run clean:dist:cli && npm run pack:cli:mac",
"dist:cli:lin": "npm run build:cli:prod && npm run clean:dist:cli && npm run pack:cli:lin",
"publish:lin": "npm run build:dist && npm run clean:dist && build --linux --x64 -p always",
"publish:mac": "npm run build:dist && npm run clean:dist && build --mac -p always",
"publish:win": "npm run build:dist && npm run clean:dist && build --win --x64 --ia32 -p always -c.win.certificateSubjectName=\"8bit Solutions LLC\""
"publish:lin": "npm run build:dist && npm run clean:dist && electron-builder --linux --x64 -p always",
"publish:mac": "npm run build:dist && npm run clean:dist && electron-builder --mac -p always",
"publish:win": "npm run build:dist && npm run clean:dist && electron-builder --win --x64 --ia32 -p always -c.win.certificateSubjectName=\"8bit Solutions LLC\"",
"prettier": "prettier --write .",
"prepare": "husky install"
},
"build": {
"extraMetadata": {
"name": "bitwarden-directory-connector"
},
"productName": "Bitwarden Directory Connector",
"appId": "com.bitwarden.directory-connector",
"copyright": "Copyright © 2015-2018 8bit Solutions LLC",
"copyright": "Copyright © 2015-2022 Bitwarden Inc.",
"directories": {
"buildResources": "resources",
"output": "dist",
"app": "build"
},
"afterSign": "scripts/notarize.js",
"mac": {
"category": "public.app-category.productivity",
"gatekeeperAssess": false,
"hardenedRuntime": true,
"entitlements": "resources/entitlements.mac.plist",
"entitlementsInherit": "resources/entitlements.mac.plist",
"target": [
"dmg",
"zip"
@@ -77,7 +90,8 @@
"target": [
"portable",
"nsis"
]
],
"sign": "scripts/sign.js"
},
"linux": {
"category": "Utility",
@@ -122,97 +136,80 @@
"artifactName": "Bitwarden-Connector-${version}-${arch}.${ext}"
}
},
"bin": {
"bwdc": "./build-cli/bwdc.js"
},
"pkg": {
"assets": "./build-cli/**/*"
},
"devDependencies": {
"@angular/compiler-cli": "^7.2.1",
"@angular/compiler-cli": "^12.2.13",
"@microsoft/microsoft-graph-types": "^1.4.0",
"@ngtools/webpack": "^7.2.2",
"@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.1.6",
"@types/node": "^10.9.4",
"@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": "3.0.14",
"electron-builder": "20.38.5",
"electron-rebuild": "^1.8.2",
"electron-reload": "^1.4.0",
"extract-text-webpack-plugin": "next",
"file-loader": "^2.0.0",
"font-awesome": "4.7.0",
"gulp": "^4.0.0",
"gulp-google-webfonts": "^2.0.0",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"node-abi": "^2.5.1",
"node-loader": "^0.6.0",
"node-sass": "^4.9.3",
"pkg": "4.3.4",
"rimraf": "^2.6.2",
"sass-loader": "^7.1.0",
"ts-loader": "^5.3.3",
"tslint": "^5.12.1",
"@ngtools/webpack": "^12.2.13",
"@types/ldapjs": "^1.0.10",
"@types/node": "^16.11.12",
"@types/proper-lockfile": "^4.1.1",
"clean-webpack-plugin": "^4.0.0",
"concurrently": "^6.0.2",
"copy-webpack-plugin": "^10.0.0",
"cross-env": "^7.0.3",
"css-loader": "^6.5.1",
"electron-builder": "^22.14.5",
"electron-notarize": "^1.1.1",
"electron-rebuild": "^3.2.5",
"electron-reload": "^1.5.0",
"html-loader": "^3.0.1",
"html-webpack-plugin": "^5.5.0",
"husky": "^7.0.4",
"lint-staged": "^12.1.3",
"mini-css-extract-plugin": "^2.4.5",
"node-loader": "^2.0.0",
"pkg": "^5.5.1",
"prebuild-install": "^5.0.0",
"prettier": "^2.5.1",
"rimraf": "^3.0.2",
"sass": "^1.32.11",
"sass-loader": "^12.4.0",
"tapable": "^1.1.3",
"ts-loader": "^9.2.5",
"tsconfig-paths-webpack-plugin": "^3.5.1",
"tslint": "~6.1.0",
"tslint-loader": "^3.5.4",
"typescript": "3.2.4",
"webpack": "^4.29.0",
"webpack-cli": "^3.2.1",
"webpack-merge": "^4.2.1",
"webpack-node-externals": "^1.7.2"
"typescript": "4.3.5",
"webpack": "^5.64.4",
"webpack-cli": "^4.9.1",
"webpack-merge": "^5.8.0",
"webpack-node-externals": "^3.0.0"
},
"dependencies": {
"@angular/animations": "7.2.1",
"@angular/common": "7.2.1",
"@angular/compiler": "7.2.1",
"@angular/core": "7.2.1",
"@angular/forms": "7.2.1",
"@angular/http": "7.2.1",
"@angular/platform-browser": "7.2.1",
"@angular/platform-browser-dynamic": "7.2.1",
"@angular/router": "7.2.1",
"@angular/upgrade": "7.2.1",
"@microsoft/microsoft-graph-client": "1.2.0",
"@okta/okta-sdk-nodejs": "1.2.0",
"angular2-toaster": "6.1.0",
"angulartics2": "6.3.0",
"big-integer": "1.6.36",
"bootstrap": "4.1.3",
"chalk": "2.4.1",
"commander": "2.18.0",
"core-js": "2.6.2",
"@angular/animations": "^12.2.13",
"@angular/cdk": "^12.2.13",
"@angular/common": "^12.2.13",
"@angular/compiler": "^12.2.13",
"@angular/core": "^12.2.13",
"@angular/forms": "^12.2.13",
"@angular/platform-browser": "^12.2.13",
"@angular/platform-browser-dynamic": "^12.2.13",
"@angular/router": "^12.2.13",
"@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",
"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.0.6",
"form-data": "2.3.2",
"googleapis": "33.0.0",
"inquirer": "6.2.0",
"keytar": "4.4.1",
"ldapjs": "1.0.2",
"lowdb": "1.0.0",
"lunr": "2.3.3",
"node-fetch": "2.2.0",
"node-forge": "0.7.6",
"rxjs": "6.3.3",
"zone.js": "0.8.28"
"form-data": "^4.0.0",
"googleapis": "^73.0.0",
"inquirer": "8.0.0",
"ldapjs": "2.3.1",
"lunr": "^2.3.9",
"ngx-toastr": "14.1.4",
"open": "^8.0.6",
"proper-lockfile": "^4.1.2",
"rxjs": "^7.4.0"
},
"engines": {
"node": "~16",
"npm": "~8"
},
"lint-staged": {
"*": "prettier --ignore-unknown --write"
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>

18
scripts/notarize.js Normal file
View File

@@ -0,0 +1,18 @@
require("dotenv").config();
const { notarize } = require("electron-notarize");
exports.default = async function notarizing(context) {
const { electronPlatformName, appOutDir } = context;
if (electronPlatformName !== "darwin") {
return;
}
const appleId = process.env.APPLE_ID_USERNAME || process.env.APPLEID;
const appleIdPassword = process.env.APPLE_ID_PASSWORD || `@keychain:AC_PASSWORD`;
const appName = context.packager.appInfo.productFilename;
return await notarize({
appBundleId: "com.bitwarden.directory-connector",
appPath: `${appOutDir}/${appName}.app`,
appleId: appleId,
appleIdPassword: appleIdPassword,
});
};

20
scripts/sign.js Normal file
View File

@@ -0,0 +1,20 @@
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",
}
);
}
};

1006
src-cli/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

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.5",
"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.7.0"
}
}

View File

@@ -0,0 +1,68 @@
import { StateService as BaseStateServiceAbstraction } from "jslib-common/abstractions/state.service";
import { StorageOptions } from "jslib-common/models/domain/storageOptions";
import { DirectoryType } from "src/enums/directoryType";
import { Account } from "src/models/account";
import { AzureConfiguration } from "src/models/azureConfiguration";
import { GSuiteConfiguration } from "src/models/gsuiteConfiguration";
import { LdapConfiguration } from "src/models/ldapConfiguration";
import { OktaConfiguration } from "src/models/oktaConfiguration";
import { OneLoginConfiguration } from "src/models/oneLoginConfiguration";
import { SyncConfiguration } from "src/models/syncConfiguration";
export abstract class StateService extends BaseStateServiceAbstraction<Account> {
getDirectory: <IConfiguration>(type: DirectoryType) => Promise<IConfiguration>;
setDirectory: (
type: DirectoryType,
config:
| LdapConfiguration
| GSuiteConfiguration
| AzureConfiguration
| OktaConfiguration
| OneLoginConfiguration
) => Promise<any>;
getLdapKey: (options?: StorageOptions) => Promise<string>;
setLdapKey: (value: string, options?: StorageOptions) => Promise<void>;
getGsuiteKey: (options?: StorageOptions) => Promise<string>;
setGsuiteKey: (value: string, options?: StorageOptions) => Promise<void>;
getAzureKey: (options?: StorageOptions) => Promise<string>;
setAzureKey: (value: string, options?: StorageOptions) => Promise<void>;
getOktaKey: (options?: StorageOptions) => Promise<string>;
setOktaKey: (value: string, options?: StorageOptions) => Promise<void>;
getOneLoginKey: (options?: StorageOptions) => Promise<string>;
setOneLoginKey: (value: string, options?: StorageOptions) => Promise<void>;
getLdapConfiguration: (options?: StorageOptions) => Promise<LdapConfiguration>;
setLdapConfiguration: (value: LdapConfiguration, options?: StorageOptions) => Promise<void>;
getGsuiteConfiguration: (options?: StorageOptions) => Promise<GSuiteConfiguration>;
setGsuiteConfiguration: (value: GSuiteConfiguration, options?: StorageOptions) => Promise<void>;
getAzureConfiguration: (options?: StorageOptions) => Promise<AzureConfiguration>;
setAzureConfiguration: (value: AzureConfiguration, options?: StorageOptions) => Promise<void>;
getOktaConfiguration: (options?: StorageOptions) => Promise<OktaConfiguration>;
setOktaConfiguration: (value: OktaConfiguration, options?: StorageOptions) => Promise<void>;
getOneLoginConfiguration: (options?: StorageOptions) => Promise<OneLoginConfiguration>;
setOneLoginConfiguration: (
value: OneLoginConfiguration,
options?: StorageOptions
) => Promise<void>;
getOrganizationId: (options?: StorageOptions) => Promise<string>;
setOrganizationId: (value: string, options?: StorageOptions) => Promise<void>;
getSync: (options?: StorageOptions) => Promise<SyncConfiguration>;
setSync: (value: SyncConfiguration, options?: StorageOptions) => Promise<void>;
getDirectoryType: (options?: StorageOptions) => Promise<DirectoryType>;
setDirectoryType: (value: DirectoryType, options?: StorageOptions) => Promise<void>;
getUserDelta: (options?: StorageOptions) => Promise<string>;
setUserDelta: (value: string, options?: StorageOptions) => Promise<void>;
getLastUserSync: (options?: StorageOptions) => Promise<Date>;
setLastUserSync: (value: Date, options?: StorageOptions) => Promise<void>;
getLastGroupSync: (options?: StorageOptions) => Promise<Date>;
setLastGroupSync: (value: Date, options?: StorageOptions) => Promise<void>;
getGroupDelta: (options?: StorageOptions) => Promise<string>;
setGroupDelta: (value: string, options?: StorageOptions) => Promise<void>;
getLastSyncHash: (options?: StorageOptions) => Promise<string>;
setLastSyncHash: (value: string, options?: StorageOptions) => Promise<void>;
getSyncingDir: (options?: StorageOptions) => Promise<boolean>;
setSyncingDir: (value: boolean, options?: StorageOptions) => Promise<void>;
clearSyncSettings: (syncHashToo: boolean) => Promise<void>;
}

View File

@@ -0,0 +1,60 @@
<div class="container-fluid">
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="row justify-content-center">
<div class="col-md-8 col-lg-6">
<p class="text-center font-weight-bold">{{ "welcome" | i18n }}</p>
<p class="text-center">{{ "logInDesc" | i18n }}</p>
<div class="card">
<h5 class="card-header">{{ "logIn" | i18n }}</h5>
<div class="card-body">
<div class="form-group">
<label for="client_id">{{ "clientId" | i18n }}</label>
<input id="client_id" name="ClientId" [(ngModel)]="clientId" class="form-control" />
</div>
<div class="form-group">
<div class="row-main">
<label for="client_secret">{{ "clientSecret" | i18n }}</label>
<div class="input-group">
<input
type="{{ showSecret ? 'text' : 'password' }}"
id="client_secret"
name="ClientSecret"
[(ngModel)]="clientSecret"
class="form-control"
/>
<div class="input-group-append">
<button
type="button"
class="ml-1 btn btn-link"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleSecret()"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="showSecret ? 'bwi-eye-slash' : 'bwi-eye'"
></i>
</button>
</div>
</div>
</div>
</div>
<div class="d-flex">
<div>
<button type="submit" class="btn btn-primary" [disabled]="form.loading">
<i class="bwi bwi-spinner bwi-fw bwi-spin" [hidden]="!form.loading"></i>
<i class="bwi bwi-sign-in bwi-fw" [hidden]="form.loading"></i>
{{ "logIn" | i18n }}
</button>
</div>
<button type="button" class="btn btn-link ml-auto" (click)="settings()">
{{ "settings" | i18n }}
</button>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
<ng-template #environment></ng-template>

View File

@@ -0,0 +1,103 @@
import { Component, Input, ViewChild, ViewContainerRef } from "@angular/core";
import { Router } from "@angular/router";
import { EnvironmentComponent } from "./environment.component";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "../../abstractions/state.service";
import { ModalService } from "jslib-angular/services/modal.service";
import { HtmlStorageLocation } from "jslib-common/enums/htmlStorageLocation";
import { Utils } from "jslib-common/misc/utils";
@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 router: Router,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private modalService: ModalService,
private logService: LogService,
private stateService: StateService
) {}
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.stateService.getEntityId();
await this.stateService.setOrganizationId(organizationId);
this.router.navigate([this.successRoute]);
} catch (e) {
this.logService.error(e);
}
}
async settings() {
const [modalRef, childComponent] = await this.modalService.openViewRef(
EnvironmentComponent,
this.environmentModal
);
childComponent.onSaved.subscribe(() => {
modalRef.close();
});
}
toggleSecret() {
this.showSecret = !this.showSecret;
document.getElementById("client_secret").focus();
}
}

View File

@@ -1,38 +1,61 @@
<div class="modal fade">
<div class="modal-dialog">
<form class="modal-content" (ngSubmit)="submit()">
<div class="modal-header">
<h3 class="modal-title">{{'settings' | i18n}}</h3>
<button type="button" class="close" data-dismiss="modal" title="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<h4>{{'selfHostedEnvironment' | i18n}}</h4>
<p>{{'selfHostedEnvironmentFooter' | i18n}}</p>
<div class="form-group">
<label for="baseUrl">{{'baseUrl' | i18n}}</label>
<input id="baseUrl" type="text" name="BaseUrl" [(ngModel)]="baseUrl" class="form-control">
<small class="text-muted form-text">{{'ex' | i18n}} https://bitwarden.company.com</small>
</div>
<h4>{{'customEnvironment' | i18n}}</h4>
<p>{{'customEnvironmentFooter' | i18n}}</p>
<div class="form-group">
<label for="apiUrl">{{'apiUrl' | i18n}}</label>
<input id="apiUrl" type="text" name="ApiUrl" [(ngModel)]="apiUrl" class="form-control">
</div>
<div class="form-group">
<label for="identityUrl">{{'identityUrl' | i18n}}</label>
<input id="identityUrl" type="text" name="IdentityUrl" [(ngModel)]="identityUrl"
class="form-control">
</div>
</div>
<div class="modal-footer justify-content-start">
<button type="submit" class="btn btn-primary">
<i class="fa fa-save fa-fw"></i>
{{'save' | i18n}}
</button>
</div>
</form>
</div>
<div class="modal-dialog">
<form class="modal-content" (ngSubmit)="submit()">
<div class="modal-header">
<h3 class="modal-title">{{ "settings" | i18n }}</h3>
<button type="button" class="close" data-dismiss="modal" title="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<h4>{{ "selfHostedEnvironment" | i18n }}</h4>
<p>{{ "selfHostedEnvironmentFooter" | i18n }}</p>
<div class="form-group">
<label for="baseUrl">{{ "baseUrl" | i18n }}</label>
<input
id="baseUrl"
type="text"
name="BaseUrl"
[(ngModel)]="baseUrl"
class="form-control"
/>
<small class="text-muted form-text"
>{{ "ex" | i18n }} https://bitwarden.company.com</small
>
</div>
<h4>{{ "customEnvironment" | i18n }}</h4>
<p>{{ "customEnvironmentFooter" | i18n }}</p>
<div class="form-group">
<label for="webVaultUrl">{{ "webVaultUrl" | i18n }}</label>
<input
id="webVaultUrl"
type="text"
name="WebVaultUrl"
[(ngModel)]="webVaultUrl"
class="form-control"
/>
</div>
<div class="form-group">
<label for="apiUrl">{{ "apiUrl" | i18n }}</label>
<input id="apiUrl" type="text" name="ApiUrl" [(ngModel)]="apiUrl" class="form-control" />
</div>
<div class="form-group">
<label for="identityUrl">{{ "identityUrl" | i18n }}</label>
<input
id="identityUrl"
type="text"
name="IdentityUrl"
[(ngModel)]="identityUrl"
class="form-control"
/>
</div>
</div>
<div class="modal-footer justify-content-start">
<button type="submit" class="btn btn-primary">
<i class="bwi bwi-save bwi-fw"></i>
{{ "save" | i18n }}
</button>
</div>
</form>
</div>
</div>

View File

@@ -1,18 +1,21 @@
import { Component } from '@angular/core';
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',
templateUrl: 'environment.component.html',
selector: "app-environment",
templateUrl: "environment.component.html",
})
export class EnvironmentComponent extends BaseEnvironmentComponent {
constructor(environmentService: EnvironmentService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService) {
super(platformUtilsService, environmentService, i18nService);
}
constructor(
environmentService: EnvironmentService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService
) {
super(platformUtilsService, environmentService, i18nService);
}
}

View File

@@ -1,35 +0,0 @@
<div class="container-fluid">
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="row justify-content-center">
<div class="col-md-8 col-lg-6">
<p class="text-center font-weight-bold">{{'welcome' | i18n}}</p>
<p class="text-center">{{'logInDesc' | i18n}}</p>
<div class="card">
<h5 class="card-header">{{'logIn' | i18n}}</h5>
<div class="card-body">
<div class="form-group">
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" class="form-control">
</div>
<div class="form-group">
<div class="row-main">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="password" name="MasterPassword"
[(ngModel)]="masterPassword" class="form-control">
</div>
</div>
<button type="submit" class="btn btn-primary" [disabled]="form.loading">
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!form.loading"></i>
<i class="fa fa-sign-in fa-fw" [hidden]="form.loading"></i>
{{'logIn' | i18n}}
</button>
<button type="button" class="btn btn-link" (click)="settings()">
{{'settings' | i18n}}
</button>
</div>
</div>
</div>
</div>
</form>
</div>
<ng-template #environment></ng-template>

View File

@@ -1,43 +0,0 @@
import {
Component,
ComponentFactoryResolver,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { Router } from '@angular/router';
import { EnvironmentComponent } from './environment.component';
import { AuthService } from 'jslib/abstractions/auth.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { LoginComponent as BaseLoginComponent } from 'jslib/angular/components/login.component';
import { ModalComponent } from 'jslib/angular/components/modal.component';
@Component({
selector: 'app-login',
templateUrl: 'login.component.html',
})
export class LoginComponent extends BaseLoginComponent {
@ViewChild('environment', { read: ViewContainerRef }) environmentModal: ViewContainerRef;
constructor(authService: AuthService, router: Router,
i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
storageService: StorageService, platformUtilsService: PlatformUtilsService) {
super(authService, router, platformUtilsService, i18nService, storageService);
super.successRoute = '/tabs/dashboard';
}
settings() {
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
const modal = this.environmentModal.createComponent(factory).instance;
const childComponent = modal.show<EnvironmentComponent>(EnvironmentComponent,
this.environmentModal);
childComponent.onSaved.subscribe(() => {
modal.close();
});
}
}

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,53 +0,0 @@
import {
Component,
ComponentFactoryResolver,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { Router } from '@angular/router';
import { TwoFactorOptionsComponent } from './two-factor-options.component';
import { TwoFactorProviderType } from 'jslib/enums/twoFactorProviderType';
import { ApiService } from 'jslib/abstractions/api.service';
import { AuthService } from 'jslib/abstractions/auth.service';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { ModalComponent } from 'jslib/angular/components/modal.component';
import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib/angular/components/two-factor.component';
@Component({
selector: 'app-two-factor',
templateUrl: 'two-factor.component.html',
})
export class TwoFactorComponent extends BaseTwoFactorComponent {
@ViewChild('twoFactorOptions', { read: ViewContainerRef }) twoFactorOptionsModal: ViewContainerRef;
constructor(authService: AuthService, router: Router,
i18nService: I18nService, apiService: ApiService,
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
private componentFactoryResolver: ComponentFactoryResolver) {
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService);
super.successRoute = '/tabs/dashboard';
}
anotherMethod() {
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
const modal = this.twoFactorOptionsModal.createComponent(factory).instance;
const childComponent = modal.show<TwoFactorOptionsComponent>(TwoFactorOptionsComponent,
this.twoFactorOptionsModal);
childComponent.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => {
modal.close();
this.selectedProviderType = provider;
await this.init();
});
childComponent.onRecoverSelected.subscribe(() => {
modal.close();
});
}
}

View File

@@ -1,60 +1,57 @@
import { NgModule } from '@angular/core';
import {
RouterModule,
Routes,
} from '@angular/router';
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuardService } from './services/auth-guard.service';
import { LaunchGuardService } from './services/launch-guard.service';
import { AuthGuardService } from "./services/auth-guard.service";
import { LaunchGuardService } from "./services/launch-guard.service";
import { LoginComponent } from './accounts/login.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 { ApiKeyComponent } from "./accounts/apiKey.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";
const routes: Routes = [
{ path: '', redirectTo: '/login', pathMatch: 'full' },
{
path: 'login',
component: LoginComponent,
canActivate: [LaunchGuardService],
},
{ path: '2fa', component: TwoFactorComponent },
{
path: 'tabs',
component: TabsComponent,
children: [
{
path: '',
redirectTo: '/tabs/dashboard',
pathMatch: 'full',
},
{
path: 'dashboard',
component: DashboardComponent,
canActivate: [AuthGuardService],
},
{
path: 'settings',
component: SettingsComponent,
canActivate: [AuthGuardService],
},
{
path: 'more',
component: MoreComponent,
canActivate: [AuthGuardService],
},
],
},
{ path: "", redirectTo: "/login", pathMatch: "full" },
{
path: "login",
component: ApiKeyComponent,
canActivate: [LaunchGuardService],
},
{
path: "tabs",
component: TabsComponent,
children: [
{
path: "",
redirectTo: "/tabs/dashboard",
pathMatch: "full",
},
{
path: "dashboard",
component: DashboardComponent,
canActivate: [AuthGuardService],
},
{
path: "settings",
component: SettingsComponent,
canActivate: [AuthGuardService],
},
{
path: "more",
component: MoreComponent,
canActivate: [AuthGuardService],
},
],
},
];
@NgModule({
imports: [RouterModule.forRoot(routes, {
useHash: true,
/*enableTracing: true,*/
})],
exports: [RouterModule],
imports: [
RouterModule.forRoot(routes, {
useHash: true,
/*enableTracing: true,*/
}),
],
exports: [RouterModule],
})
export class AppRoutingModule { }
export class AppRoutingModule {}

View File

@@ -1,204 +1,161 @@
import {
BodyOutputType,
Toast,
ToasterConfig,
ToasterContainerComponent,
ToasterService,
} from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
Component,
NgZone,
OnInit,
SecurityContext,
ViewChild,
ViewContainerRef,
} from "@angular/core";
import { DomSanitizer } from "@angular/platform-browser";
import { Router } from "@angular/router";
import { IndividualConfig, ToastrService } from "ngx-toastr";
import {
Component,
ComponentFactoryResolver,
NgZone,
OnInit,
SecurityContext,
Type,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { Router } from '@angular/router';
import { AuthService } from "jslib-common/abstractions/auth.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.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 { ModalComponent } from 'jslib/angular/components/modal.component';
import { SyncService } from "../services/sync.service";
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
import { StateService } from "../abstractions/state.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 { ConfigurationService } from '../services/configuration.service';
import { SyncService } from '../services/sync.service';
const BroadcasterSubscriptionId = 'AppComponent';
const BroadcasterSubscriptionId = "AppComponent";
@Component({
selector: 'app-root',
styles: [],
template: `
<toaster-container [toasterconfig]="toasterConfig"></toaster-container>
<ng-template #settings></ng-template>
<router-outlet></router-outlet>`,
selector: "app-root",
styles: [],
template: ` <ng-template #settings></ng-template>
<router-outlet></router-outlet>`,
})
export class AppComponent implements OnInit {
@ViewChild('settings', { read: ViewContainerRef }) settingsRef: ViewContainerRef;
@ViewChild("settings", { read: ViewContainerRef, static: true }) settingsRef: ViewContainerRef;
toasterConfig: ToasterConfig = new ToasterConfig({
showCloseButton: true,
mouseoverTimerStop: true,
animation: 'flyRight',
limit: 5,
});
constructor(
private broadcasterService: BroadcasterService,
private tokenService: TokenService,
private authService: AuthService,
private router: Router,
private toastrService: ToastrService,
private i18nService: I18nService,
private sanitizer: DomSanitizer,
private ngZone: NgZone,
private platformUtilsService: PlatformUtilsService,
private messagingService: MessagingService,
private syncService: SyncService,
private stateService: StateService,
private logService: LogService
) {}
private lastActivity: number = null;
private modal: ModalComponent = null;
ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case "syncScheduleStarted":
case "syncScheduleStopped":
this.stateService.setSyncingDir(message.command === "syncScheduleStarted");
break;
case "logout":
this.logOut(!!message.expired);
break;
case "checkDirSync":
try {
const syncConfig = await this.stateService.getSync();
if (syncConfig.interval == null || syncConfig.interval < 5) {
return;
}
constructor(private angulartics2GoogleAnalytics: Angulartics2GoogleAnalytics,
private broadcasterService: BroadcasterService, private userService: UserService,
private tokenService: TokenService, private storageService: StorageService,
private authService: AuthService, private router: Router, private analytics: Angulartics2,
private toasterService: ToasterService, private i18nService: I18nService,
private sanitizer: DomSanitizer, private ngZone: NgZone,
private componentFactoryResolver: ComponentFactoryResolver, private messagingService: MessagingService,
private configurationService: ConfigurationService, private syncService: SyncService,
private stateService: StateService, private apiService: ApiService) {
(window as any).BitwardenToasterService = toasterService;
}
ngOnInit() {
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');
break;
case 'logout':
this.logOut(!!message.expired);
break;
case 'checkDirSync':
try {
const syncConfig = await this.configurationService.getSync();
if (syncConfig.interval == null || syncConfig.interval < 5) {
return;
}
const syncInterval = syncConfig.interval * 60000;
const lastGroupSync = await this.configurationService.getLastGroupSyncDate();
const lastUserSync = await this.configurationService.getLastUserSyncDate();
let lastSync: Date = null;
if (lastGroupSync != null && lastUserSync == null) {
lastSync = lastGroupSync;
} else if (lastGroupSync == null && lastUserSync != null) {
lastSync = lastUserSync;
} else if (lastGroupSync != null && lastUserSync != null) {
if (lastGroupSync.getTime() < lastUserSync.getTime()) {
lastSync = lastGroupSync;
} else {
lastSync = lastUserSync;
}
}
let lastSyncAgo = syncInterval + 1;
if (lastSync != null) {
lastSyncAgo = new Date().getTime() - lastSync.getTime();
}
if (lastSyncAgo >= syncInterval) {
await this.syncService.sync(false, false);
}
} catch { }
this.messagingService.send('scheduleNextDirSync');
break;
case 'showToast':
this.showToast(message);
break;
case 'analyticsEventTrack':
this.analytics.eventTrack.next({
action: message.action,
properties: { label: message.label },
});
break;
default:
const syncInterval = syncConfig.interval * 60000;
const lastGroupSync = await this.stateService.getLastGroupSync();
const lastUserSync = await this.stateService.getLastUserSync();
let lastSync: Date = null;
if (lastGroupSync != null && lastUserSync == null) {
lastSync = lastGroupSync;
} else if (lastGroupSync == null && lastUserSync != null) {
lastSync = lastUserSync;
} else if (lastGroupSync != null && lastUserSync != null) {
if (lastGroupSync.getTime() < lastUserSync.getTime()) {
lastSync = lastGroupSync;
} else {
lastSync = lastUserSync;
}
}
let lastSyncAgo = syncInterval + 1;
if (lastSync != null) {
lastSyncAgo = new Date().getTime() - lastSync.getTime();
}
if (lastSyncAgo >= syncInterval) {
await this.syncService.sync(false, false);
}
} catch (e) {
this.logService.error(e);
}
this.messagingService.send("scheduleNextDirSync");
break;
case "showToast":
this.showToast(message);
break;
case "ssoCallback":
this.router.navigate(["sso"], {
queryParams: { code: message.code, state: message.state },
});
});
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
private async logOut(expired: boolean) {
const userId = await this.userService.getUserId();
await Promise.all([
this.tokenService.clearToken(),
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'));
}
this.router.navigate(['login']);
});
}
private openModal<T>(type: Type<T>, ref: ViewContainerRef) {
if (this.modal != null) {
this.modal.close();
break;
default:
}
});
});
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = ref.createComponent(factory).instance;
const childComponent = this.modal.show<T>(type, ref);
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
this.modal.onClosed.subscribe(() => {
this.modal = null;
});
private async logOut(expired: boolean) {
await this.tokenService.clearToken();
await this.stateService.clean();
this.authService.logOut(async () => {
if (expired) {
this.platformUtilsService.showToast(
"warning",
this.i18nService.t("loggedOut"),
this.i18nService.t("loginExpired")
);
}
this.router.navigate(["login"]);
});
}
private showToast(msg: any) {
let message = "";
const options: Partial<IndividualConfig> = {};
if (typeof msg.text === "string") {
message = msg.text;
} else if (msg.text.length === 1) {
message = msg.text[0];
} else {
msg.text.forEach(
(t: string) =>
(message += "<p>" + this.sanitizer.sanitize(SecurityContext.HTML, t) + "</p>")
);
options.enableHtml = true;
}
if (msg.options != null) {
if (msg.options.trustedHtml === true) {
options.enableHtml = true;
}
if (msg.options.timeout != null && msg.options.timeout > 0) {
options.timeOut = msg.options.timeout;
}
}
private showToast(msg: any) {
const toast: Toast = {
type: msg.type,
title: msg.title,
};
if (typeof (msg.text) === 'string') {
toast.body = msg.text;
} else if (msg.text.length === 1) {
toast.body = msg.text[0];
} else {
let message = '';
msg.text.forEach((t: string) =>
message += ('<p>' + this.sanitizer.sanitize(SecurityContext.HTML, t) + '</p>'));
toast.body = message;
toast.bodyOutputType = BodyOutputType.TrustedHtml;
}
if (msg.options != null) {
if (msg.options.trustedHtml === true) {
toast.bodyOutputType = BodyOutputType.TrustedHtml;
}
if (msg.options.timeout != null && msg.options.timeout > 0) {
toast.timeout = msg.options.timeout;
}
}
this.toasterService.popAsync(toast);
}
this.toastrService.show(message, msg.title, options, "toast-" + msg.type);
}
}

View File

@@ -1,85 +1,74 @@
import 'core-js';
import 'zone.js/dist/zone';
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";
import { AppRoutingModule } from './app-routing.module';
import { ServicesModule } from './services/services.module';
import { NgModule } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AppComponent } from "./app.component";
import { AppComponent } from './app.component';
import { CalloutComponent } from "jslib-angular/components/callout.component";
import { IconComponent } from "jslib-angular/components/icon.component";
import { BitwardenToastModule } from "jslib-angular/components/toastr.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 { DashboardComponent } from "./tabs/dashboard.component";
import { MoreComponent } from "./tabs/more.component";
import { SettingsComponent } from "./tabs/settings.component";
import { TabsComponent } from "./tabs/tabs.component";
import { EnvironmentComponent } from './accounts/environment.component';
import { LoginComponent } from './accounts/login.component';
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component';
import { TwoFactorComponent } from './accounts/two-factor.component';
import { DashboardComponent } from './tabs/dashboard.component';
import { 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 { 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: [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
AppRoutingModule,
ServicesModule,
Angulartics2Module.forRoot([Angulartics2GoogleAnalytics], {
pageTracking: {
clearQueryParams: true,
},
}),
ToasterModule.forRoot(),
],
declarations: [
ApiActionDirective,
AppComponent,
AutofocusDirective,
BlurClickDirective,
BoxRowDirective,
DashboardComponent,
EnvironmentComponent,
FallbackSrcDirective,
I18nPipe,
IconComponent,
LoginComponent,
ModalComponent,
MoreComponent,
SearchCiphersPipe,
SettingsComponent,
StopClickDirective,
StopPropDirective,
TabsComponent,
TwoFactorComponent,
TwoFactorOptionsComponent,
],
entryComponents: [
EnvironmentComponent,
ModalComponent,
TwoFactorOptionsComponent,
],
providers: [],
bootstrap: [AppComponent],
imports: [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
AppRoutingModule,
ServicesModule,
BitwardenToastModule.forRoot({
maxOpened: 5,
autoDismiss: true,
closeButton: true,
}),
],
declarations: [
A11yTitleDirective,
ApiActionDirective,
ApiKeyComponent,
AppComponent,
AutofocusDirective,
BlurClickDirective,
BoxRowDirective,
CalloutComponent,
DashboardComponent,
EnvironmentComponent,
FallbackSrcDirective,
I18nPipe,
IconComponent,
MoreComponent,
SearchCiphersPipe,
SettingsComponent,
StopClickDirective,
StopPropDirective,
TabsComponent,
],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule { }
export class AppModule {}

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,15 +1,15 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
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');
require("../scss/styles.scss");
import { AppModule } from './app.module';
import { AppModule } from "./app.module";
if (!isDev()) {
enableProdMode();
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule, { preserveWhitespaces: true });

View File

@@ -1,24 +1,21 @@
import { Injectable } from '@angular/core';
import {
CanActivate,
Router,
} from '@angular/router';
import { Injectable } from "@angular/core";
import { CanActivate } from "@angular/router";
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { UserService } from 'jslib/abstractions/user.service';
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { StateService } from "../../abstractions/state.service";
@Injectable()
export class AuthGuardService implements CanActivate {
constructor(private userService: UserService, private router: Router,
private messagingService: MessagingService) { }
constructor(private stateService: StateService, private messagingService: MessagingService) {}
async canActivate() {
const isAuthed = await this.userService.isAuthenticated();
if (!isAuthed) {
this.messagingService.send('logout');
return false;
}
return true;
async canActivate() {
const isAuthed = await this.stateService.getIsAuthenticated();
if (!isAuthed) {
this.messagingService.send("logout");
return false;
}
return true;
}
}

View File

@@ -1,22 +1,19 @@
import { Injectable } from '@angular/core';
import {
CanActivate,
Router,
} from '@angular/router';
import { Injectable } from "@angular/core";
import { CanActivate, Router } from "@angular/router";
import { UserService } from 'jslib/abstractions/user.service';
import { StateService } from "../../abstractions/state.service";
@Injectable()
export class LaunchGuardService implements CanActivate {
constructor(private userService: UserService, private router: Router) { }
constructor(private stateService: StateService, private router: Router) {}
async canActivate() {
const isAuthed = await this.userService.isAuthenticated();
if (!isAuthed) {
return true;
}
this.router.navigate(['/tabs/dashboard']);
return false;
async canActivate() {
const isAuthed = await this.stateService.getIsAuthenticated();
if (!isAuthed) {
return true;
}
this.router.navigate(["/tabs/dashboard"]);
return false;
}
}

View File

@@ -1,146 +1,237 @@
import { remote } from 'electron';
import { APP_INITIALIZER, Injector, NgModule } from "@angular/core";
import {
APP_INITIALIZER,
NgModule,
} from '@angular/core';
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 { ToasterModule } from 'angular2-toaster';
import { AuthGuardService } from "./auth-guard.service";
import { LaunchGuardService } from "./launch-guard.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 { ElectronStorageService } from 'jslib/electron/services/electronStorage.service';
import { I18nService } from "../../services/i18n.service";
import { SyncService } from "../../services/sync.service";
import { AuthGuardService } from './auth-guard.service';
import { LaunchGuardService } from './launch-guard.service';
import { JslibServicesModule } from "jslib-angular/services/jslib-services.module";
import { ConfigurationService } from '../../services/configuration.service';
import { I18nService } from '../../services/i18n.service';
import { SyncService } from '../../services/sync.service';
import { ContainerService } from "jslib-common/services/container.service";
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
import { ValidationService } from 'jslib/angular/services/validation.service';
import { NodeApiService } from "jslib-node/services/nodeApi.service";
import { NodeCryptoFunctionService } from "jslib-node/services/nodeCryptoFunction.service";
import { Analytics } from 'jslib/misc/analytics';
import { ApiService as ApiServiceAbstraction } from "jslib-common/abstractions/api.service";
import { AppIdService as AppIdServiceAbstraction } from "jslib-common/abstractions/appId.service";
import { AuthService as AuthServiceAbstraction } from "jslib-common/abstractions/auth.service";
import { BroadcasterService as BroadcasterServiceAbstraction } from "jslib-common/abstractions/broadcaster.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 { KeyConnectorService as KeyConnectorServiceAbstraction } from "jslib-common/abstractions/keyConnector.service";
import { LogService as LogServiceAbstraction } from "jslib-common/abstractions/log.service";
import { MessagingService as MessagingServiceAbstraction } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "jslib-common/abstractions/platformUtils.service";
import { StateMigrationService as StateMigrationServiceAbstraction } from "jslib-common/abstractions/stateMigration.service";
import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service";
import { TokenService as TokenServiceAbstraction } from "jslib-common/abstractions/token.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "jslib-common/abstractions/vaultTimeout.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 { StateService } from 'jslib/services/state.service';
import { TokenService } from 'jslib/services/token.service';
import { UserService } from 'jslib/services/user.service';
import { StateService as StateServiceAbstraction } from "../../abstractions/state.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 { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib/abstractions/platformUtils.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';
import { ApiService, refreshToken } from "../../services/api.service";
import { AuthService } from "../../services/auth.service";
import { StateService } from "../../services/state.service";
import { StateMigrationService } from "../../services/stateMigration.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 platformUtilsService = new ElectronPlatformUtilsService(i18nService, messagingService, true);
const storageService: StorageServiceAbstraction = new ElectronStorageService(remote.app.getPath('userData'));
const secureStorageService: StorageServiceAbstraction = new ElectronRendererSecureStorageService();
const cryptoFunctionService: CryptoFunctionServiceAbstraction = new NodeCryptoFunctionService();
const cryptoService = new CryptoService(storageService, secureStorageService, cryptoFunctionService);
const appIdService = new AppIdService(storageService);
const tokenService = new TokenService(storageService);
const apiService = new ApiService(tokenService, platformUtilsService,
async (expired: boolean) => messagingService.send('logout', { expired: expired }));
const environmentService = new EnvironmentService(apiService, storageService, null);
const userService = new UserService(tokenService, storageService);
const containerService = new ContainerService(cryptoService);
const authService = new AuthService(cryptoService, apiService, userService, tokenService, appIdService,
i18nService, platformUtilsService, messagingService, false);
const configurationService = new ConfigurationService(storageService, secureStorageService);
const syncService = new SyncService(configurationService, logService, cryptoFunctionService, apiService,
messagingService, i18nService);
import { Account } from "../../models/account";
const analytics = new Analytics(window, () => true, platformUtilsService, storageService, appIdService);
containerService.attachToWindow(window);
import { StateFactory } from "jslib-common/factories/stateFactory";
export function initFactory(): Function {
return async () => {
await environmentService.setUrlsFromStorage();
await i18nService.init();
authService.init();
const htmlEl = window.document.documentElement;
htmlEl.classList.add('os_' + platformUtilsService.getDeviceString());
htmlEl.classList.add('locale_' + i18nService.translationLocale);
window.document.title = i18nService.t('bitwardenDirectoryConnector');
import { GlobalState } from "jslib-common/models/domain/globalState";
let installAction = null;
const installedVersion = await storageService.get<string>(ConstantsService.installedVersionKey);
const currentVersion = platformUtilsService.getApplicationVersion();
if (installedVersion == null) {
installAction = 'install';
} else if (installedVersion !== currentVersion) {
installAction = 'update';
}
function refreshTokenCallback(injector: Injector) {
return () => {
const stateService = injector.get(StateServiceAbstraction);
const authService = injector.get(AuthServiceAbstraction);
return refreshToken(stateService, authService);
};
}
if (installAction != null) {
await storageService.save(ConstantsService.installedVersionKey, currentVersion);
}
export function initFactory(
environmentService: EnvironmentServiceAbstraction,
i18nService: I18nService,
authService: AuthService,
platformUtilsService: PlatformUtilsServiceAbstraction,
stateService: StateServiceAbstraction,
cryptoService: CryptoServiceAbstraction
): Function {
return async () => {
await stateService.init();
await environmentService.setUrlsFromStorage();
await i18nService.init();
authService.init();
const htmlEl = window.document.documentElement;
htmlEl.classList.add("os_" + platformUtilsService.getDeviceString());
htmlEl.classList.add("locale_" + i18nService.translationLocale);
window.document.title = i18nService.t("bitwardenDirectoryConnector");
window.setTimeout(async () => {
if (await userService.isAuthenticated()) {
const profile = await apiService.getProfile();
stateService.save('profileOrganizations', profile.organizations);
}
}, 500);
};
let installAction = null;
const installedVersion = await stateService.getInstalledVersion();
const currentVersion = await platformUtilsService.getApplicationVersion();
if (installedVersion == null) {
installAction = "install";
} else if (installedVersion !== currentVersion) {
installAction = "update";
}
if (installAction != null) {
await stateService.setInstalledVersion(currentVersion);
}
const containerService = new ContainerService(cryptoService);
containerService.attachToWindow(window);
};
}
@NgModule({
imports: [
ToasterModule,
],
declarations: [],
providers: [
ValidationService,
AuthGuardService,
LaunchGuardService,
{ provide: AuthServiceAbstraction, useValue: authService },
{ provide: EnvironmentServiceAbstraction, useValue: environmentService },
{ provide: TokenServiceAbstraction, useValue: tokenService },
{ provide: I18nServiceAbstraction, useValue: i18nService },
{ provide: CryptoServiceAbstraction, useValue: cryptoService },
{ provide: PlatformUtilsServiceAbstraction, useValue: platformUtilsService },
{ provide: ApiServiceAbstraction, useValue: apiService },
{ provide: UserServiceAbstraction, useValue: userService },
{ provide: MessagingServiceAbstraction, useValue: messagingService },
{ provide: BroadcasterService, useValue: broadcasterService },
{ provide: StorageServiceAbstraction, useValue: storageService },
{ provide: StateServiceAbstraction, useValue: stateService },
{ provide: LogServiceAbstraction, useValue: logService },
{ provide: ConfigurationService, useValue: configurationService },
{ provide: SyncService, useValue: syncService },
{
provide: APP_INITIALIZER,
useFactory: initFactory,
deps: [],
multi: true,
},
],
imports: [JslibServicesModule],
declarations: [],
providers: [
{
provide: APP_INITIALIZER,
useFactory: initFactory,
deps: [
EnvironmentServiceAbstraction,
I18nServiceAbstraction,
AuthServiceAbstraction,
PlatformUtilsServiceAbstraction,
StateServiceAbstraction,
CryptoServiceAbstraction,
],
multi: true,
},
{ provide: LogServiceAbstraction, useClass: ElectronLogService, deps: [] },
{
provide: I18nServiceAbstraction,
useFactory: (window: Window) => new I18nService(window.navigator.language, "./locales"),
deps: ["WINDOW"],
},
{
provide: MessagingServiceAbstraction,
useClass: ElectronRendererMessagingService,
deps: [BroadcasterServiceAbstraction],
},
{ provide: StorageServiceAbstraction, useClass: ElectronRendererStorageService },
{ provide: "SECURE_STORAGE", useClass: ElectronRendererSecureStorageService },
{
provide: PlatformUtilsServiceAbstraction,
useFactory: (
i18nService: I18nServiceAbstraction,
messagingService: MessagingServiceAbstraction,
stateService: StateServiceAbstraction
) => new ElectronPlatformUtilsService(i18nService, messagingService, true, stateService),
deps: [I18nServiceAbstraction, MessagingServiceAbstraction, StateServiceAbstraction],
},
{ provide: CryptoFunctionServiceAbstraction, useClass: NodeCryptoFunctionService, deps: [] },
{
provide: ApiServiceAbstraction,
useFactory: (
tokenService: TokenServiceAbstraction,
platformUtilsService: PlatformUtilsServiceAbstraction,
environmentService: EnvironmentServiceAbstraction,
messagingService: MessagingServiceAbstraction,
injector: Injector
) =>
new NodeApiService(
tokenService,
platformUtilsService,
environmentService,
async (expired: boolean) => messagingService.send("logout", { expired: expired }),
"Bitwarden_DC/" +
platformUtilsService.getApplicationVersion() +
" (" +
platformUtilsService.getDeviceString().toUpperCase() +
")",
refreshTokenCallback(injector)
),
deps: [
TokenServiceAbstraction,
PlatformUtilsServiceAbstraction,
EnvironmentServiceAbstraction,
MessagingServiceAbstraction,
Injector,
],
},
{
provide: AuthServiceAbstraction,
useClass: AuthService,
deps: [
CryptoServiceAbstraction,
ApiServiceAbstraction,
TokenServiceAbstraction,
AppIdServiceAbstraction,
I18nServiceAbstraction,
PlatformUtilsServiceAbstraction,
MessagingServiceAbstraction,
VaultTimeoutServiceAbstraction,
LogServiceAbstraction,
CryptoFunctionServiceAbstraction,
EnvironmentServiceAbstraction,
KeyConnectorServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: SyncService,
useClass: SyncService,
deps: [
LogServiceAbstraction,
CryptoFunctionServiceAbstraction,
ApiServiceAbstraction,
MessagingServiceAbstraction,
I18nServiceAbstraction,
EnvironmentServiceAbstraction,
StateServiceAbstraction,
],
},
AuthGuardService,
LaunchGuardService,
{
provide: StateMigrationServiceAbstraction,
useFactory: (
storageService: StorageServiceAbstraction,
secureStorageService: StorageServiceAbstraction
) =>
new StateMigrationService(
storageService,
secureStorageService,
new StateFactory(GlobalState, Account)
),
deps: [StorageServiceAbstraction, "SECURE_STORAGE"],
},
{
provide: StateServiceAbstraction,
useFactory: (
storageService: StorageServiceAbstraction,
secureStorageService: StorageServiceAbstraction,
logService: LogServiceAbstraction,
stateMigrationService: StateMigrationServiceAbstraction
) =>
new StateService(
storageService,
secureStorageService,
logService,
stateMigrationService,
true,
new StateFactory(GlobalState, Account)
),
deps: [
StorageServiceAbstraction,
"SECURE_STORAGE",
LogServiceAbstraction,
StateMigrationServiceAbstraction,
],
},
],
})
export class ServicesModule {
}
export class ServicesModule {}

View File

@@ -1,90 +1,110 @@
<div class="card mb-3">
<h3 class="card-header">{{'sync' | i18n}}</h3>
<div class="card-body">
<p>
{{'lastGroupSync' | i18n}}:
<span *ngIf="!lastGroupSync">-</span>
{{lastGroupSync | date:'medium'}}
<br /> {{'lastUserSync' | i18n}}:
<span *ngIf="!lastUserSync">-</span>
{{lastUserSync | date:'medium'}}
</p>
<p>
{{'syncStatus' | i18n}}:
<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>
<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>
</div>
<h3 class="card-header">{{ "sync" | i18n }}</h3>
<div class="card-body">
<p>
{{ "lastGroupSync" | i18n }}:
<span *ngIf="!lastGroupSync">-</span>
{{ lastGroupSync | date: "medium" }}
<br />
{{ "lastUserSync" | i18n }}:
<span *ngIf="!lastUserSync">-</span>
{{ lastUserSync | date: "medium" }}
</p>
<p>
{{ "syncStatus" | i18n }}:
<strong *ngIf="syncRunning" class="text-success">{{ "running" | i18n }}</strong>
<strong *ngIf="!syncRunning" class="text-danger">{{ "stopped" | i18n }}</strong>
</p>
<form #startForm [appApiAction]="startPromise" class="d-inline">
<button (click)="start()" class="btn btn-primary" [disabled]="startForm.loading">
<i class="bwi bwi-play bwi-fw" [hidden]="startForm.loading"></i>
<i class="bwi bwi-spinner bwi-fw bwi-spin" [hidden]="!startForm.loading"></i>
{{ "startSync" | i18n }}
</button>
</form>
<button (click)="stop()" class="btn btn-primary">
<i class="bwi bwi-stop bwi-fw"></i>
{{ "stopSync" | i18n }}
</button>
<form #syncForm [appApiAction]="syncPromise" class="d-inline">
<button (click)="sync()" class="btn btn-primary" [disabled]="syncForm.loading">
<i class="bwi bwi-refresh bwi-fw" [ngClass]="{ 'bwi-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>
<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)">
<hr />
<div class="row">
<div class="col-lg">
<h4>{{'users' | i18n}}</h4>
<ul class="fa-ul testing-list" *ngIf="simEnabledUsers && simEnabledUsers.length">
<li *ngFor="let u of simEnabledUsers" title="{{u.referenceId}}">
<i class="fa-li fa fa-user"></i>
{{u.displayName}}
</li>
</ul>
<p *ngIf="!simEnabledUsers || !simEnabledUsers.length">{{'noUsers' | i18n}}</p>
<h4>{{'disabledUsers' | i18n}}</h4>
<ul class="fa-ul testing-list" *ngIf="simDisabledUsers && simDisabledUsers.length">
<li *ngFor="let u of simDisabledUsers" title="{{u.referenceId}}">
<i class="fa-li fa fa-user"></i>
{{u.displayName}}
</li>
</ul>
<p *ngIf="!simDisabledUsers || !simDisabledUsers.length">{{'noUsers' | i18n}}</p>
<h4>{{'deletedUsers' | i18n}}</h4>
<ul class="fa-ul testing-list" *ngIf="simDeletedUsers && simDeletedUsers.length">
<li *ngFor="let u of simDeletedUsers" title="{{u.referenceId}}">
<i class="fa-li fa fa-user"></i>
{{u.displayName}}
</li>
</ul>
<p *ngIf="!simDeletedUsers || !simDeletedUsers.length">{{'noUsers' | i18n}}</p>
</div>
<div class="col-lg">
<h4>{{'groups' | i18n}}</h4>
<ul class="fa-ul testing-list" *ngIf="simGroups && simGroups.length">
<li *ngFor="let g of simGroups" title="{{g.referenceId}}">
<i class="fa-li fa fa-sitemap"></i>
{{g.displayName}}
<ul class="small" *ngIf="g.users && g.users.length">
<li *ngFor="let u of g.users" title="{{u.referenceId}}">{{u.displayName}}</li>
</ul>
</li>
</ul>
<p *ngIf="!simGroups || !simGroups.length">{{'noGroups' | i18n}}</p>
</div>
</div>
</ng-container>
<h3 class="card-header">{{ "testing" | i18n }}</h3>
<div class="card-body">
<p>{{ "testingDesc" | i18n }}</p>
<form #simForm [appApiAction]="simPromise" class="d-inline">
<button (click)="simulate()" class="btn btn-primary" [disabled]="simForm.loading">
<i class="bwi bwi-spinner bwi-fw bwi-spin" [hidden]="!simForm.loading"></i>
<i class="bwi bwi-bug bwi-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="!simForm.loading && (simUsers || simGroups)">
<hr />
<div class="row">
<div class="col-lg">
<h4>{{ "users" | i18n }}</h4>
<ul class="bwi-ul testing-list" *ngIf="simEnabledUsers && simEnabledUsers.length">
<li *ngFor="let u of simEnabledUsers" title="{{ u.referenceId }}">
<i class="bwi bwi-li bwi-user"></i>
{{ u.displayName }}
</li>
</ul>
<p *ngIf="!simEnabledUsers || !simEnabledUsers.length">
{{ "noUsers" | i18n }}
</p>
<h4>{{ "disabledUsers" | i18n }}</h4>
<ul class="bwi-ul testing-list" *ngIf="simDisabledUsers && simDisabledUsers.length">
<li *ngFor="let u of simDisabledUsers" title="{{ u.referenceId }}">
<i class="bwi bwi-li bwi-user"></i>
{{ u.displayName }}
</li>
</ul>
<p *ngIf="!simDisabledUsers || !simDisabledUsers.length">
{{ "noUsers" | i18n }}
</p>
<h4>{{ "deletedUsers" | i18n }}</h4>
<ul class="bwi-ul testing-list" *ngIf="simDeletedUsers && simDeletedUsers.length">
<li *ngFor="let u of simDeletedUsers" title="{{ u.referenceId }}">
<i class="bwi bwi-li bwi-user"></i>
{{ u.displayName }}
</li>
</ul>
<p *ngIf="!simDeletedUsers || !simDeletedUsers.length">
{{ "noUsers" | i18n }}
</p>
</div>
<div class="col-lg">
<h4>{{ "groups" | i18n }}</h4>
<ul class="bwi-ul testing-list" *ngIf="simGroups && simGroups.length">
<li *ngFor="let g of simGroups" title="{{ g.referenceId }}">
<i class="bwi bwi-li bwi-sitemap"></i>
{{ g.displayName }}
<ul class="small" *ngIf="g.users && g.users.length">
<li *ngFor="let u of g.users" title="{{ u.referenceId }}">
{{ u.displayName }}
</li>
</ul>
</li>
</ul>
<p *ngIf="!simGroups || !simGroups.length">{{ "noGroups" | i18n }}</p>
</div>
</div>
</ng-container>
</div>
</div>

View File

@@ -1,126 +1,127 @@
import {
ChangeDetectorRef,
Component,
NgZone,
OnDestroy,
OnInit,
} from '@angular/core';
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core";
import { ToasterService } from 'angular2-toaster';
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.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 { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { StateService } from 'jslib/abstractions/state.service';
import { SyncService } from "../../services/sync.service";
import { SyncService } from '../../services/sync.service';
import { GroupEntry } from "../../models/groupEntry";
import { SimResult } from "../../models/simResult";
import { UserEntry } from "../../models/userEntry";
import { GroupEntry } from '../../models/groupEntry';
import { UserEntry } from '../../models/userEntry';
import { ConfigurationService } from '../../services/configuration.service';
import { ConnectorUtils } from "../../utils";
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
import { StateService } from "../../abstractions/state.service";
import { ConnectorUtils } from '../../utils';
const BroadcasterSubscriptionId = 'DashboardComponent';
const BroadcasterSubscriptionId = "DashboardComponent";
@Component({
selector: 'app-dashboard',
templateUrl: 'dashboard.component.html',
selector: "app-dashboard",
templateUrl: "dashboard.component.html",
})
export class DashboardComponent implements OnInit, OnDestroy {
simGroups: GroupEntry[];
simUsers: UserEntry[];
simEnabledUsers: UserEntry[] = [];
simDisabledUsers: UserEntry[] = [];
simDeletedUsers: UserEntry[] = [];
simPromise: Promise<any>;
simSinceLast: boolean = false;
syncPromise: Promise<[GroupEntry[], UserEntry[]]>;
startPromise: Promise<any>;
lastGroupSync: Date;
lastUserSync: Date;
syncRunning: boolean;
simGroups: GroupEntry[];
simUsers: UserEntry[];
simEnabledUsers: UserEntry[] = [];
simDisabledUsers: UserEntry[] = [];
simDeletedUsers: UserEntry[] = [];
simPromise: Promise<SimResult>;
simSinceLast: boolean = false;
syncPromise: Promise<[GroupEntry[], UserEntry[]]>;
startPromise: Promise<any>;
lastGroupSync: Date;
lastUserSync: Date;
syncRunning: boolean;
constructor(private i18nService: I18nService, private syncService: SyncService,
private configurationService: ConfigurationService, private broadcasterService: BroadcasterService,
private ngZone: NgZone, private messagingService: MessagingService,
private toasterService: ToasterService, private changeDetectorRef: ChangeDetectorRef,
private stateService: StateService) { }
constructor(
private i18nService: I18nService,
private syncService: SyncService,
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
private messagingService: MessagingService,
private platformUtilsService: PlatformUtilsService,
private changeDetectorRef: ChangeDetectorRef,
private stateService: StateService
) {}
async ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case 'dirSyncCompleted':
this.updateLastSync();
break;
default:
break;
}
async ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case "dirSyncCompleted":
this.updateLastSync();
break;
default:
break;
}
this.changeDetectorRef.detectChanges();
});
});
this.changeDetectorRef.detectChanges();
});
});
this.syncRunning = !!(await this.stateService.get('syncingDir'));
this.updateLastSync();
this.syncRunning = !!(await this.stateService.getSyncingDir());
this.updateLastSync();
}
async ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async start() {
this.startPromise = this.syncService.sync(false, false);
await this.startPromise;
this.messagingService.send("scheduleNextDirSync");
this.syncRunning = true;
this.platformUtilsService.showToast("success", null, this.i18nService.t("syncingStarted"));
}
async stop() {
this.messagingService.send("cancelDirSync");
this.syncRunning = false;
this.platformUtilsService.showToast("success", null, this.i18nService.t("syncingStopped"));
}
async sync() {
this.syncPromise = this.syncService.sync(false, false);
const result = await this.syncPromise;
const groupCount = result[0] != null ? result[0].length : 0;
const userCount = result[1] != null ? result[1].length : 0;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("syncCounts", groupCount.toString(), userCount.toString())
);
}
async simulate() {
this.simGroups = [];
this.simUsers = [];
this.simEnabledUsers = [];
this.simDisabledUsers = [];
this.simDeletedUsers = [];
try {
this.simPromise = ConnectorUtils.simulate(
this.syncService,
this.i18nService,
this.simSinceLast
);
const result = await this.simPromise;
this.simGroups = result.groups;
this.simUsers = result.users;
this.simEnabledUsers = result.enabledUsers;
this.simDisabledUsers = result.disabledUsers;
this.simDeletedUsers = result.deletedUsers;
} catch (e) {
this.simGroups = null;
this.simUsers = null;
}
}
async ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async start() {
this.startPromise = this.syncService.sync(false, false);
await this.startPromise;
this.messagingService.send('scheduleNextDirSync');
this.syncRunning = true;
this.toasterService.popAsync('success', null, this.i18nService.t('syncingStarted'));
}
async stop() {
this.messagingService.send('cancelDirSync');
this.syncRunning = false;
this.toasterService.popAsync('success', null, this.i18nService.t('syncingStopped'));
}
async sync() {
this.syncPromise = this.syncService.sync(false, false);
const result = await this.syncPromise;
const groupCount = result[0] != null ? result[0].length : 0;
const userCount = result[1] != null ? result[1].length : 0;
this.toasterService.popAsync('success', null,
this.i18nService.t('syncCounts', groupCount.toString(), userCount.toString()));
}
async simulate() {
this.simGroups = [];
this.simUsers = [];
this.simEnabledUsers = [];
this.simDisabledUsers = [];
this.simDeletedUsers = [];
this.simPromise = new Promise(async (resolve, reject) => {
try {
const result = await ConnectorUtils.simulate(this.syncService, this.i18nService, this.simSinceLast);
this.simGroups = result.groups;
this.simUsers = result.users;
this.simEnabledUsers = result.enabledUsers;
this.simDisabledUsers = result.disabledUsers;
this.simDeletedUsers = result.deletedUsers;
} catch (e) {
this.simGroups = null;
this.simUsers = null;
reject(e);
return;
}
resolve();
});
}
private async updateLastSync() {
this.lastGroupSync = await this.configurationService.getLastGroupSyncDate();
this.lastUserSync = await this.configurationService.getLastUserSyncDate();
}
private async updateLastSync() {
this.lastGroupSync = await this.stateService.getLastGroupSync();
this.lastUserSync = await this.stateService.getLastUserSync();
}
}

View File

@@ -1,32 +1,38 @@
<div class="row">
<div class="col-sm">
<div class="card">
<h3 class="card-header">{{'about' | i18n}}</h3>
<div class="card-body">
<p>
{{'bitwardenDirectoryConnector' | i18n}}
<br /> {{'version' | i18n : version}}
<br /> &copy; 8bit Solutions LLC 2015-{{year}}
</p>
<button class="btn btn-primary" type="button" (click)="update()" [disabled]="checkingForUpdate">
<i class="fa fa-download fa-fw" [hidden]="checkingForUpdate"></i>
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!checkingForUpdate"></i>
{{'checkForUpdates' | i18n}}
</button>
</div>
</div>
<div class="col-sm">
<div class="card">
<h3 class="card-header">{{ "about" | i18n }}</h3>
<div class="card-body">
<p>
{{ "bitwardenDirectoryConnector" | i18n }}
<br />
{{ "version" | i18n: version }} <br />
&copy; Bitwarden Inc. LLC 2015-{{ year }}
</p>
<button
class="btn btn-primary"
type="button"
(click)="update()"
[disabled]="checkingForUpdate"
>
<i class="bwi bwi-download bwi-fw" [hidden]="checkingForUpdate"></i>
<i class="bwi bwi-spinner bwi-fw bwi-spin" [hidden]="!checkingForUpdate"></i>
{{ "checkForUpdates" | i18n }}
</button>
</div>
</div>
<div class="col-sm">
<div class="card">
<h3 class="card-header">{{'other' | i18n}}</h3>
<div class="card-body">
<button class="btn btn-primary" type="button" (click)="logOut()">
{{'logOut' | i18n}}
</button>
<button class="btn btn-primary" type="button" (click)="clearCache()">
{{'clearSyncCache' | i18n}}
</button>
</div>
</div>
</div>
<div class="col-sm">
<div class="card">
<h3 class="card-header">{{ "other" | i18n }}</h3>
<div class="card-body">
<button class="btn btn-primary" type="button" (click)="logOut()">
{{ "logOut" | i18n }}
</button>
<button class="btn btn-primary" type="button" (click)="clearCache()">
{{ "clearSyncCache" | i18n }}
</button>
</div>
</div>
</div>
</div>

View File

@@ -1,78 +1,77 @@
import {
ChangeDetectorRef,
Component,
NgZone,
OnDestroy,
OnInit,
} from '@angular/core';
import { ChangeDetectorRef, Component, NgZone, OnInit } from "@angular/core";
import { ToasterService } from 'angular2-toaster';
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.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 { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
import { StateService } from "../../abstractions/state.service";
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { ConfigurationService } from '../../services/configuration.service';
const BroadcasterSubscriptionId = 'MoreComponent';
const BroadcasterSubscriptionId = "MoreComponent";
@Component({
selector: 'app-more',
templateUrl: 'more.component.html',
selector: "app-more",
templateUrl: "more.component.html",
})
export class MoreComponent implements OnInit {
version: string;
year: string;
checkingForUpdate = false;
version: string;
year: string;
checkingForUpdate = false;
constructor(private platformUtilsService: PlatformUtilsService, private i18nService: I18nService,
private messagingService: MessagingService, private configurationService: ConfigurationService,
private toasterService: ToasterService, private broadcasterService: BroadcasterService,
private ngZone: NgZone, private changeDetectorRef: ChangeDetectorRef) { }
constructor(
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private messagingService: MessagingService,
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
private changeDetectorRef: ChangeDetectorRef,
private stateService: StateService
) {}
ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case 'checkingForUpdate':
this.checkingForUpdate = true;
break;
case 'doneCheckingForUpdate':
this.checkingForUpdate = false;
break;
default:
break;
}
this.changeDetectorRef.detectChanges();
});
});
this.year = new Date().getFullYear().toString();
this.version = this.platformUtilsService.getApplicationVersion();
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
update() {
this.messagingService.send('checkForUpdate');
}
async logOut() {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('logOutConfirmation'), this.i18nService.t('logOut'),
this.i18nService.t('yes'), this.i18nService.t('cancel'));
if (confirmed) {
this.messagingService.send('logout');
async ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case "checkingForUpdate":
this.checkingForUpdate = true;
break;
case "doneCheckingForUpdate":
this.checkingForUpdate = false;
break;
default:
break;
}
}
async clearCache() {
await this.configurationService.clearStatefulSettings(true);
this.toasterService.popAsync('success', null, this.i18nService.t('syncCacheCleared'));
this.changeDetectorRef.detectChanges();
});
});
this.year = new Date().getFullYear().toString();
this.version = await this.platformUtilsService.getApplicationVersion();
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
update() {
this.messagingService.send("checkForUpdate");
}
async logOut() {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("logOutConfirmation"),
this.i18nService.t("logOut"),
this.i18nService.t("yes"),
this.i18nService.t("cancel")
);
if (confirmed) {
this.messagingService.send("logout");
}
}
async clearCache() {
await this.stateService.clearSyncSettings(true);
this.platformUtilsService.showToast("success", null, this.i18nService.t("syncCacheCleared"));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,132 +1,154 @@
import {
ChangeDetectorRef,
Component,
NgZone,
OnDestroy,
OnInit,
} from '@angular/core';
import { ChangeDetectorRef, Component, NgZone, OnDestroy, 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 { LogService } from "jslib-common/abstractions/log.service";
import { ProfileOrganizationResponse } from 'jslib/models/response/profileOrganizationResponse';
import { DirectoryType } from "../../enums/directoryType";
import { ConfigurationService } from '../../services/configuration.service';
import { AzureConfiguration } from "../../models/azureConfiguration";
import { GSuiteConfiguration } from "../../models/gsuiteConfiguration";
import { LdapConfiguration } from "../../models/ldapConfiguration";
import { OktaConfiguration } from "../../models/oktaConfiguration";
import { OneLoginConfiguration } from "../../models/oneLoginConfiguration";
import { SyncConfiguration } from "../../models/syncConfiguration";
import { DirectoryType } from '../../enums/directoryType';
import { AzureConfiguration } from '../../models/azureConfiguration';
import { GSuiteConfiguration } from '../../models/gsuiteConfiguration';
import { LdapConfiguration } from '../../models/ldapConfiguration';
import { OktaConfiguration } from '../../models/oktaConfiguration';
import { SyncConfiguration } from '../../models/syncConfiguration';
import { ConnectorUtils } from '../../utils';
import { StateService } from "../../abstractions/state.service";
import { ConnectorUtils } from "../../utils";
@Component({
selector: 'app-settings',
templateUrl: 'settings.component.html',
selector: "app-settings",
templateUrl: "settings.component.html",
})
export class SettingsComponent implements OnInit, OnDestroy {
directory: DirectoryType;
directoryType = DirectoryType;
ldap = new LdapConfiguration();
gsuite = new GSuiteConfiguration();
azure = new AzureConfiguration();
okta = new OktaConfiguration();
sync = new SyncConfiguration();
organizationId: string;
directoryOptions: any[];
organizationOptions: any[];
directory: DirectoryType;
directoryType = DirectoryType;
ldap = new LdapConfiguration();
gsuite = new GSuiteConfiguration();
azure = new AzureConfiguration();
okta = new OktaConfiguration();
oneLogin = new OneLoginConfiguration();
sync = new SyncConfiguration();
directoryOptions: 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,
private stateService: StateService) {
this.directoryOptions = [
{ name: i18nService.t('select'), value: null },
{ name: 'Active Directory / LDAP', value: DirectoryType.Ldap },
{ name: 'Azure Active Directory', value: DirectoryType.AzureActiveDirectory },
{ name: 'G Suite (Google)', value: DirectoryType.GSuite },
{ name: 'Okta', value: DirectoryType.Okta },
];
constructor(
private i18nService: I18nService,
private changeDetectorRef: ChangeDetectorRef,
private ngZone: NgZone,
private logService: LogService,
private stateService: StateService
) {
this.directoryOptions = [
{ name: this.i18nService.t("select"), value: null },
{ name: "Active Directory / LDAP", value: DirectoryType.Ldap },
{ name: "Azure Active Directory", value: DirectoryType.AzureActiveDirectory },
{ name: "G Suite (Google)", value: DirectoryType.GSuite },
{ name: "Okta", value: DirectoryType.Okta },
{ name: "OneLogin", value: DirectoryType.OneLogin },
];
}
async ngOnInit() {
this.directory = await this.stateService.getDirectoryType();
this.ldap =
(await this.stateService.getDirectory<LdapConfiguration>(DirectoryType.Ldap)) || this.ldap;
this.gsuite =
(await this.stateService.getDirectory<GSuiteConfiguration>(DirectoryType.GSuite)) ||
this.gsuite;
this.azure =
(await this.stateService.getDirectory<AzureConfiguration>(
DirectoryType.AzureActiveDirectory
)) || this.azure;
this.okta =
(await this.stateService.getDirectory<OktaConfiguration>(DirectoryType.Okta)) || this.okta;
this.oneLogin =
(await this.stateService.getDirectory<OneLoginConfiguration>(DirectoryType.OneLogin)) ||
this.oneLogin;
this.sync = (await this.stateService.getSync()) || this.sync;
}
async ngOnDestroy() {
await this.submit();
}
async submit() {
ConnectorUtils.adjustConfigForSave(this.ldap, this.sync);
if (this.ldap != null && this.ldap.ad) {
this.ldap.pagedSearch = true;
}
await this.stateService.setDirectoryType(this.directory);
await this.stateService.setDirectory(DirectoryType.Ldap, this.ldap);
await this.stateService.setDirectory(DirectoryType.GSuite, this.gsuite);
await this.stateService.setDirectory(DirectoryType.AzureActiveDirectory, this.azure);
await this.stateService.setDirectory(DirectoryType.Okta, this.okta);
await this.stateService.setDirectory(DirectoryType.OneLogin, this.oneLogin);
await this.stateService.setSync(this.sync);
}
parseKeyFile() {
const filePicker = document.getElementById("keyFile") as HTMLInputElement;
if (filePicker.files == null || filePicker.files.length < 0) {
return;
}
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 });
}
const reader = new FileReader();
reader.readAsText(filePicker.files[0], "utf-8");
reader.onload = (evt) => {
this.ngZone.run(async () => {
try {
const result = JSON.parse((evt.target as FileReader).result as string);
if (result.client_email != null && result.private_key != null) {
this.gsuite.clientEmail = result.client_email;
this.gsuite.privateKey = result.private_key;
}
} catch (e) {
this.logService.error(e);
}
this.changeDetectorRef.detectChanges();
});
this.organizationId = await this.configurationService.getOrganizationId();
this.directory = await this.configurationService.getDirectoryType();
this.ldap = (await this.configurationService.getDirectory<LdapConfiguration>(DirectoryType.Ldap)) ||
this.ldap;
this.gsuite = (await this.configurationService.getDirectory<GSuiteConfiguration>(DirectoryType.GSuite)) ||
this.gsuite;
this.azure = (await this.configurationService.getDirectory<AzureConfiguration>(
DirectoryType.AzureActiveDirectory)) || this.azure;
this.okta = (await this.configurationService.getDirectory<OktaConfiguration>(
DirectoryType.Okta)) || this.okta;
this.sync = (await this.configurationService.getSync()) || this.sync;
// reset file input
// ref: https://stackoverflow.com/a/20552042
filePicker.type = "";
filePicker.type = "file";
filePicker.value = "";
};
}
setSslPath(id: string) {
const filePicker = document.getElementById(id + "_file") as HTMLInputElement;
if (filePicker.files == null || filePicker.files.length < 0) {
return;
}
async ngOnDestroy() {
await this.submit();
}
(this.ldap as any)[id] = filePicker.files[0].path;
// reset file input
// ref: https://stackoverflow.com/a/20552042
filePicker.type = "";
filePicker.type = "file";
filePicker.value = "";
}
async submit() {
ConnectorUtils.adjustConfigForSave(this.ldap, this.sync);
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);
await this.configurationService.saveDirectory(DirectoryType.AzureActiveDirectory, this.azure);
await this.configurationService.saveDirectory(DirectoryType.Okta, this.okta);
await this.configurationService.saveSync(this.sync);
}
toggleLdapPassword() {
this.showLdapPassword = !this.showLdapPassword;
document.getElementById("password").focus();
}
parseKeyFile() {
const filePicker = (document.getElementById('keyFile') as HTMLInputElement);
if (filePicker.files == null || filePicker.files.length < 0) {
return;
}
toggleAzureKey() {
this.showAzureKey = !this.showAzureKey;
document.getElementById("secretKey").focus();
}
const reader = new FileReader();
reader.readAsText(filePicker.files[0], 'utf-8');
reader.onload = (evt) => {
this.ngZone.run(async () => {
try {
const result = JSON.parse((evt.target as FileReader).result as string);
if (result.client_email != null && result.private_key != null) {
this.gsuite.clientEmail = result.client_email;
this.gsuite.privateKey = result.private_key;
}
} catch { }
this.changeDetectorRef.detectChanges();
});
toggleOktaKey() {
this.showOktaKey = !this.showOktaKey;
document.getElementById("oktaToken").focus();
}
// reset file input
// ref: https://stackoverflow.com/a/20552042
filePicker.type = '';
filePicker.type = 'file';
filePicker.value = '';
};
}
setSslPath(id: string) {
const filePicker = (document.getElementById(id + '_file') as HTMLInputElement);
if (filePicker.files == null || filePicker.files.length < 0) {
return;
}
(this.ldap as any)[id] = filePicker.files[0].path;
// reset file input
// ref: https://stackoverflow.com/a/20552042
filePicker.type = '';
filePicker.type = 'file';
filePicker.value = '';
}
toggleOneLoginSecret() {
this.showOneLoginSecret = !this.showOneLoginSecret;
document.getElementById("oneLoginClientSecret").focus();
}
}

View File

@@ -1,23 +1,23 @@
<div class="container-fluid">
<ul class="nav nav-tabs mb-3">
<li class="nav-item">
<a class="nav-link" routerLink="dashboard" routerLinkActive="active">
<i class="fa fa-dashboard"></i>
{{'dashboard' | i18n}}
</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="settings" routerLinkActive="active">
<i class="fa fa-cogs"></i>
{{'settings' | i18n}}
</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="more" routerLinkActive="active">
<i class="fa fa-sliders"></i>
{{'more' | i18n}}
</a>
</li>
</ul>
<router-outlet></router-outlet>
<ul class="nav nav-tabs mb-3">
<li class="nav-item">
<a class="nav-link" routerLink="dashboard" routerLinkActive="active">
<i class="bwi bwi-dashboard"></i>
{{ "dashboard" | i18n }}
</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="settings" routerLinkActive="active">
<i class="bwi bwi-cogs"></i>
{{ "settings" | i18n }}
</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="more" routerLinkActive="active">
<i class="bwi bwi-sliders"></i>
{{ "more" | i18n }}
</a>
</li>
</ul>
<router-outlet></router-outlet>
</div>

View File

@@ -1,7 +1,7 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
@Component({
selector: 'app-tabs',
templateUrl: 'tabs.component.html',
selector: "app-tabs",
templateUrl: "tabs.component.html",
})
export class TabsComponent { }
export class TabsComponent {}

View File

@@ -1,132 +1,294 @@
import * as fs from 'fs';
import * as path from 'path';
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 { SyncService } from './services/sync.service';
import { I18nService } from "./services/i18n.service";
import { KeytarSecureStorageService } from "./services/keytarSecureStorage.service";
import { LowdbStorageService } from "./services/lowdbStorage.service";
import { StateService } from "./services/state.service";
import { StateMigrationService } from "./services/stateMigration.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 { NodeApiService } from "jslib-node/services/nodeApi.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 { TokenService } from 'jslib/services/token.service';
import { UserService } from 'jslib/services/user.service';
import { AppIdService } from "jslib-common/services/appId.service";
import { CipherService } from "jslib-common/services/cipher.service";
import { CollectionService } from "jslib-common/services/collection.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 { FileUploadService } from "jslib-common/services/fileUpload.service";
import { FolderService } from "jslib-common/services/folder.service";
import { KeyConnectorService } from "jslib-common/services/keyConnector.service";
import { NoopMessagingService } from "jslib-common/services/noopMessaging.service";
import { OrganizationService } from "jslib-common/services/organization.service";
import { PasswordGenerationService } from "jslib-common/services/passwordGeneration.service";
import { PolicyService } from "jslib-common/services/policy.service";
import { ProviderService } from "jslib-common/services/provider.service";
import { SearchService } from "jslib-common/services/search.service";
import { SendService } from "jslib-common/services/send.service";
import { SettingsService } from "jslib-common/services/settings.service";
import { TokenService } from "jslib-common/services/token.service";
import { Program } from './program';
import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service";
import { Program } from "./program";
import { Account } from "./models/account";
import { GlobalStateFactory } from "jslib-common/factories/globalStateFactory";
import { StateFactory } from "jslib-common/factories/stateFactory";
import { GlobalState } from "jslib-common/models/domain/globalState";
// tslint:disable-next-line
const packageJson = require('./package.json');
const packageJson = require("./package.json");
export let searchService: SearchService = null;
export class Main {
dataFilePath: string;
logService: ConsoleLogService;
messagingService: NoopMessagingService;
storageService: LowdbStorageService;
secureStorageService: KeytarSecureStorageService;
i18nService: I18nService;
platformUtilsService: CliPlatformUtilsService;
constantsService: ConstantsService;
cryptoService: CryptoService;
tokenService: TokenService;
appIdService: AppIdService;
apiService: NodeApiService;
environmentService: EnvironmentService;
userService: UserService;
containerService: ContainerService;
cryptoFunctionService: NodeCryptoFunctionService;
authService: AuthService;
configurationService: ConfigurationService;
syncService: SyncService;
program: Program;
dataFilePath: string;
logService: ConsoleLogService;
messagingService: NoopMessagingService;
storageService: LowdbStorageService;
secureStorageService: StorageServiceAbstraction;
i18nService: I18nService;
platformUtilsService: CliPlatformUtilsService;
cryptoService: CryptoService;
tokenService: TokenService;
appIdService: AppIdService;
apiService: NodeApiService;
environmentService: EnvironmentService;
containerService: ContainerService;
cryptoFunctionService: NodeCryptoFunctionService;
authService: AuthService;
collectionService: CollectionService;
cipherService: CipherService;
fileUploadService: FileUploadService;
folderService: FolderService;
searchService: SearchService;
sendService: SendService;
settingsService: SettingsService;
syncService: SyncService;
passwordGenerationService: PasswordGenerationService;
policyService: PolicyService;
keyConnectorService: KeyConnectorService;
program: Program;
stateService: StateService;
stateMigrationService: StateMigrationService;
organizationService: OrganizationService;
providerService: ProviderService;
constructor() {
const applicationName = 'Bitwarden Directory Connector';
if (process.env.BITWARDENCLI_CONNECTOR_APPDATA_DIR) {
this.dataFilePath = path.resolve(process.env.BITWARDENCLI_CONNECTOR_APPDATA_DIR);
} else if (process.env.BITWARDEN_CONNECTOR_APPDATA_DIR) {
this.dataFilePath = path.resolve(process.env.BITWARDEN_CONNECTOR_APPDATA_DIR);
} else if (fs.existsSync(path.join(__dirname, 'bitwarden-connector-appdata'))) {
this.dataFilePath = path.join(__dirname, 'bitwarden-connector-appdata');
} else if (process.platform === 'darwin') {
this.dataFilePath = path.join(process.env.HOME, 'Library/Application Support/' + applicationName);
} else if (process.platform === 'win32') {
this.dataFilePath = path.join(process.env.APPDATA, applicationName);
} else if (process.env.XDG_CONFIG_HOME) {
this.dataFilePath = path.join(process.env.XDG_CONFIG_HOME, applicationName);
} else {
this.dataFilePath = path.join(process.env.HOME, '.config/' + applicationName);
}
this.i18nService = new I18nService('en', './locales');
this.platformUtilsService = new CliPlatformUtilsService('connector', packageJson);
this.logService = new ConsoleLogService(this.platformUtilsService.isDev(),
(level) => process.env.BWCLI_DEBUG !== 'true' && level <= LogLevelType.Info);
this.cryptoFunctionService = new NodeCryptoFunctionService();
this.storageService = new LowdbStorageService(null, this.dataFilePath, true);
this.secureStorageService = new KeytarSecureStorageService(applicationName);
this.cryptoService = new CryptoService(this.storageService, this.secureStorageService,
this.cryptoFunctionService);
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.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, true);
this.configurationService = new ConfigurationService(this.storageService, this.secureStorageService);
this.syncService = new SyncService(this.configurationService, this.logService, this.cryptoFunctionService,
this.apiService, this.messagingService, this.i18nService);
this.program = new Program(this);
constructor() {
const applicationName = "Bitwarden Directory Connector";
if (process.env.BITWARDENCLI_CONNECTOR_APPDATA_DIR) {
this.dataFilePath = path.resolve(process.env.BITWARDENCLI_CONNECTOR_APPDATA_DIR);
} else if (process.env.BITWARDEN_CONNECTOR_APPDATA_DIR) {
this.dataFilePath = path.resolve(process.env.BITWARDEN_CONNECTOR_APPDATA_DIR);
} else if (fs.existsSync(path.join(__dirname, "bitwarden-connector-appdata"))) {
this.dataFilePath = path.join(__dirname, "bitwarden-connector-appdata");
} else if (process.platform === "darwin") {
this.dataFilePath = path.join(
process.env.HOME,
"Library/Application Support/" + applicationName
);
} else if (process.platform === "win32") {
this.dataFilePath = path.join(process.env.APPDATA, applicationName);
} else if (process.env.XDG_CONFIG_HOME) {
this.dataFilePath = path.join(process.env.XDG_CONFIG_HOME, applicationName);
} else {
this.dataFilePath = path.join(process.env.HOME, ".config/" + applicationName);
}
async run() {
await this.init();
this.program.run();
}
const plaintextSecrets = process.env.BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS === "true";
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
);
this.cryptoFunctionService = new NodeCryptoFunctionService();
this.storageService = new LowdbStorageService(
this.logService,
null,
this.dataFilePath,
false,
true
);
this.secureStorageService = plaintextSecrets
? this.storageService
: new KeytarSecureStorageService(applicationName);
async logout() {
await Promise.all([
this.tokenService.clearToken(),
this.userService.clear(),
]);
}
this.stateMigrationService = new StateMigrationService(
this.storageService,
this.secureStorageService,
new StateFactory(GlobalState, Account)
);
private async init() {
this.storageService.init();
this.containerService.attachToWindow(global);
await this.environmentService.setUrlsFromStorage();
// Dev Server URLs. Comment out the line above.
// this.apiService.setUrls({
// base: null,
// api: 'http://localhost:4000',
// identity: 'http://localhost:33656',
// });
const locale = await this.storageService.get<string>(ConstantsService.localeKey);
await this.i18nService.init(locale);
this.authService.init();
this.stateService = new StateService(
this.storageService,
this.secureStorageService,
this.logService,
this.stateMigrationService,
process.env.BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS !== "true",
new StateFactory(GlobalState, Account)
);
const installedVersion = await this.storageService.get<string>(ConstantsService.installedVersionKey);
const currentVersion = this.platformUtilsService.getApplicationVersion();
if (installedVersion == null || installedVersion !== currentVersion) {
await this.storageService.save(ConstantsService.installedVersionKey, currentVersion);
}
this.cryptoService = new CryptoService(
this.cryptoFunctionService,
this.platformUtilsService,
this.logService,
this.stateService
);
this.appIdService = new AppIdService(this.storageService);
this.tokenService = new TokenService(this.stateService);
this.messagingService = new NoopMessagingService();
this.environmentService = new EnvironmentService(this.stateService);
this.apiService = new NodeApiService(
this.tokenService,
this.platformUtilsService,
this.environmentService,
async (expired: boolean) => await this.logout(),
"Bitwarden_DC/" +
this.platformUtilsService.getApplicationVersion() +
" (" +
this.platformUtilsService.getDeviceString().toUpperCase() +
")",
(clientId, clientSecret) => this.authService.logInApiKey(clientId, clientSecret)
);
this.containerService = new ContainerService(this.cryptoService);
this.organizationService = new OrganizationService(this.stateService);
this.keyConnectorService = new KeyConnectorService(
this.stateService,
this.cryptoService,
this.apiService,
this.tokenService,
this.logService,
this.organizationService
);
this.authService = new AuthService(
this.cryptoService,
this.apiService,
this.tokenService,
this.appIdService,
this.i18nService,
this.platformUtilsService,
this.messagingService,
null,
this.logService,
this.cryptoFunctionService,
this.environmentService,
this.keyConnectorService,
this.stateService
);
this.syncService = new SyncService(
this.logService,
this.cryptoFunctionService,
this.apiService,
this.messagingService,
this.i18nService,
this.environmentService,
this.stateService
);
this.policyService = new PolicyService(
this.stateService,
this.organizationService,
this.apiService
);
this.passwordGenerationService = new PasswordGenerationService(
this.cryptoService,
this.policyService,
this.stateService
);
this.settingsService = new SettingsService(this.stateService);
this.fileUploadService = new FileUploadService(this.logService, this.apiService);
this.cipherService = new CipherService(
this.cryptoService,
this.settingsService,
this.apiService,
this.fileUploadService,
this.i18nService,
() => searchService,
this.logService,
this.stateService
);
this.searchService = new SearchService(this.cipherService, this.logService, this.i18nService);
this.folderService = new FolderService(
this.cryptoService,
this.apiService,
this.i18nService,
this.cipherService,
this.stateService
);
this.collectionService = new CollectionService(
this.cryptoService,
this.i18nService,
this.stateService
);
this.sendService = new SendService(
this.cryptoService,
this.apiService,
this.fileUploadService,
this.i18nService,
this.cryptoFunctionService,
this.stateService
);
this.providerService = new ProviderService(this.stateService);
this.program = new Program(this);
}
async run() {
await this.init();
this.program.run();
}
async logout() {
await this.tokenService.clearToken();
await this.stateService.clean();
}
private async init() {
await this.storageService.init();
await this.stateService.init();
this.containerService.attachToWindow(global);
await this.environmentService.setUrlsFromStorage();
// Dev Server URLs. Comment out the line above.
// this.apiService.setUrls({
// base: null,
// api: 'http://localhost:4000',
// identity: 'http://localhost:33656',
// });
const locale = await this.stateService.getLocale();
await this.i18nService.init(locale);
this.authService.init();
const installedVersion = await this.stateService.getInstalledVersion();
const currentVersion = await this.platformUtilsService.getApplicationVersion();
if (installedVersion == null || installedVersion !== currentVersion) {
await this.stateService.setInstalledVersion(currentVersion);
}
}
}
const main = new Main();

View File

@@ -1,22 +1,21 @@
import * as program from 'commander';
import * as program from "commander";
import { I18nService } from 'jslib/abstractions/i18n.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { ConfigurationService } from '../services/configuration.service';
import { 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 { StateService } from "../abstractions/state.service";
export class ClearCacheCommand {
constructor(private configurationService: ConfigurationService, private i18nService: I18nService) { }
constructor(private i18nService: I18nService, private stateService: StateService) {}
async run(cmd: program.Command): Promise<Response> {
try {
await this.configurationService.clearStatefulSettings(true);
const res = new MessageResponse(this.i18nService.t('syncCacheCleared'), null);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
async run(cmd: program.OptionValues): Promise<Response> {
try {
await this.stateService.clearSyncSettings(true);
const res = new MessageResponse(this.i18nService.t("syncCacheCleared"), null);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
}
}

View File

@@ -1,127 +1,157 @@
import * as program from 'commander';
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 { StateService } from "../abstractions/state.service";
import { DirectoryType } from '../enums/directoryType';
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';
import { LdapConfiguration } from '../models/ldapConfiguration';
import { OktaConfiguration } from '../models/oktaConfiguration';
import { SyncConfiguration } from '../models/syncConfiguration';
import { AzureConfiguration } from "../models/azureConfiguration";
import { GSuiteConfiguration } from "../models/gsuiteConfiguration";
import { LdapConfiguration } from "../models/ldapConfiguration";
import { OktaConfiguration } from "../models/oktaConfiguration";
import { OneLoginConfiguration } from "../models/oneLoginConfiguration";
import { SyncConfiguration } from "../models/syncConfiguration";
import { ConnectorUtils } from '../utils';
import { ConnectorUtils } from "../utils";
import { NodeUtils } from "jslib-common/misc/nodeUtils";
export class ConfigCommand {
private directory: DirectoryType;
private ldap = new LdapConfiguration();
private gsuite = new GSuiteConfiguration();
private azure = new AzureConfiguration();
private okta = new OktaConfiguration();
private sync = new SyncConfiguration();
private directory: DirectoryType;
private ldap = new LdapConfiguration();
private gsuite = new GSuiteConfiguration();
private azure = new AzureConfiguration();
private okta = new OktaConfiguration();
private oneLogin = new OneLoginConfiguration();
private sync = new SyncConfiguration();
constructor(private environmentService: EnvironmentService, private i18nService: I18nService,
private configurationService: ConfigurationService) { }
constructor(
private environmentService: EnvironmentService,
private i18nService: I18nService,
private stateService: StateService
) {}
async run(setting: string, value: string, cmd: program.Command): Promise<Response> {
setting = setting.toLowerCase();
try {
switch (setting) {
case 'server':
await this.setServer(value);
break;
case 'directory':
await this.setDirectory(value);
break;
case 'ldap.password':
await this.setLdapPassword(value);
break;
case 'gsuite.key':
await this.setGSuiteKey(value);
break;
case 'azure.key':
await this.setAzureKey(value);
break;
case 'okta.token':
await this.setOktaToken(value);
break;
default:
return Response.badRequest('Unknown setting.');
}
} catch (e) {
return Response.error(e);
}
const res = new MessageResponse(this.i18nService.t('savedSetting', setting), null);
return Response.success(res);
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];
}
}
private async setServer(url: string) {
url = (url === 'null' || url === 'bitwarden.com' || url === 'https://bitwarden.com' ? null : url);
await this.environmentService.setUrls({
base: url,
});
try {
switch (setting) {
case "server":
await this.setServer(value);
break;
case "directory":
await this.setDirectory(value);
break;
case "ldap.password":
await this.setLdapPassword(value);
break;
case "gsuite.key":
await this.setGSuiteKey(value);
break;
case "azure.key":
await this.setAzureKey(value);
break;
case "okta.token":
await this.setOktaToken(value);
break;
case "onelogin.secret":
await this.setOneLoginSecret(value);
break;
default:
return Response.badRequest("Unknown setting.");
}
} catch (e) {
return Response.error(e);
}
const res = new MessageResponse(this.i18nService.t("savedSetting", setting), null);
return Response.success(res);
}
private async setDirectory(type: string) {
const dir = parseInt(type, null);
if (dir < DirectoryType.Ldap || dir > DirectoryType.Okta) {
throw new Error('Invalid directory type value.');
}
await this.loadConfig();
this.directory = dir;
await this.saveConfig();
}
private async setServer(url: string) {
url = url === "null" || url === "bitwarden.com" || url === "https://bitwarden.com" ? null : url;
await this.environmentService.setUrls({
base: url,
});
}
private async setLdapPassword(password: string) {
await this.loadConfig();
this.ldap.password = password;
await this.saveConfig();
private async setDirectory(type: string) {
const dir = parseInt(type, null);
if (dir < DirectoryType.Ldap || dir > DirectoryType.OneLogin) {
throw new Error("Invalid directory type value.");
}
await this.loadConfig();
this.directory = dir;
await this.saveConfig();
}
private async setGSuiteKey(key: string) {
await this.loadConfig();
this.gsuite.privateKey = key;
await this.saveConfig();
}
private async setLdapPassword(password: string) {
await this.loadConfig();
this.ldap.password = password;
await this.saveConfig();
}
private async setAzureKey(key: string) {
await this.loadConfig();
this.azure.key = key;
await this.saveConfig();
}
private async setGSuiteKey(key: string) {
await this.loadConfig();
this.gsuite.privateKey = key != null ? key.trimLeft() : null;
await this.saveConfig();
}
private async setOktaToken(token: string) {
await this.loadConfig();
this.okta.token = token;
await this.saveConfig();
}
private async setAzureKey(key: string) {
await this.loadConfig();
this.azure.key = key;
await this.saveConfig();
}
private async loadConfig() {
this.directory = await this.configurationService.getDirectoryType();
this.ldap = (await this.configurationService.getDirectory<LdapConfiguration>(DirectoryType.Ldap)) ||
this.ldap;
this.gsuite = (await this.configurationService.getDirectory<GSuiteConfiguration>(DirectoryType.GSuite)) ||
this.gsuite;
this.azure = (await this.configurationService.getDirectory<AzureConfiguration>(
DirectoryType.AzureActiveDirectory)) || this.azure;
this.okta = (await this.configurationService.getDirectory<OktaConfiguration>(
DirectoryType.Okta)) || this.okta;
this.sync = (await this.configurationService.getSync()) || this.sync;
}
private async setOktaToken(token: string) {
await this.loadConfig();
this.okta.token = token;
await this.saveConfig();
}
private async saveConfig() {
ConnectorUtils.adjustConfigForSave(this.ldap, this.sync);
await this.configurationService.saveDirectoryType(this.directory);
await this.configurationService.saveDirectory(DirectoryType.Ldap, this.ldap);
await this.configurationService.saveDirectory(DirectoryType.GSuite, this.gsuite);
await this.configurationService.saveDirectory(DirectoryType.AzureActiveDirectory, this.azure);
await this.configurationService.saveDirectory(DirectoryType.Okta, this.okta);
await this.configurationService.saveSync(this.sync);
}
private async setOneLoginSecret(secret: string) {
await this.loadConfig();
this.oneLogin.clientSecret = secret;
await this.saveConfig();
}
private async loadConfig() {
this.directory = await this.stateService.getDirectoryType();
this.ldap =
(await this.stateService.getDirectory<LdapConfiguration>(DirectoryType.Ldap)) || this.ldap;
this.gsuite =
(await this.stateService.getDirectory<GSuiteConfiguration>(DirectoryType.GSuite)) ||
this.gsuite;
this.azure =
(await this.stateService.getDirectory<AzureConfiguration>(
DirectoryType.AzureActiveDirectory
)) || this.azure;
this.okta =
(await this.stateService.getDirectory<OktaConfiguration>(DirectoryType.Okta)) || this.okta;
this.oneLogin =
(await this.stateService.getDirectory<OneLoginConfiguration>(DirectoryType.OneLogin)) ||
this.oneLogin;
this.sync = (await this.stateService.getSync()) || this.sync;
}
private async saveConfig() {
ConnectorUtils.adjustConfigForSave(this.ldap, this.sync);
await this.stateService.setDirectoryType(this.directory);
await this.stateService.setDirectory(DirectoryType.Ldap, this.ldap);
await this.stateService.setDirectory(DirectoryType.GSuite, this.gsuite);
await this.stateService.setDirectory(DirectoryType.AzureActiveDirectory, this.azure);
await this.stateService.setDirectory(DirectoryType.Okta, this.okta);
await this.stateService.setDirectory(DirectoryType.OneLogin, this.oneLogin);
await this.stateService.setSync(this.sync);
}
}

View File

@@ -1,29 +1,31 @@
import * as program from 'commander';
import * as program from "commander";
import { ConfigurationService } from '../services/configuration.service';
import { StateService } from "../abstractions/state.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) { }
constructor(private stateService: StateService) {}
async run(object: string, cmd: program.Command): Promise<Response> {
try {
switch (object.toLowerCase()) {
case 'groups':
const groupsDate = await this.configurationService.getLastGroupSyncDate();
const groupsRes = new StringResponse(groupsDate == null ? null : groupsDate.toISOString());
return Response.success(groupsRes);
case 'users':
const usersDate = await this.configurationService.getLastUserSyncDate();
const usersRes = new StringResponse(usersDate == null ? null : usersDate.toISOString());
return Response.success(usersRes);
default:
return Response.badRequest('Unknown object.');
}
} catch (e) {
return Response.error(e);
}
async run(object: string): Promise<Response> {
try {
switch (object.toLowerCase()) {
case "groups":
const groupsDate = await this.stateService.getLastGroupSync();
const groupsRes = new StringResponse(
groupsDate == null ? null : groupsDate.toISOString()
);
return Response.success(groupsRes);
case "users":
const usersDate = await this.stateService.getLastUserSync();
const usersRes = new StringResponse(usersDate == null ? null : usersDate.toISOString());
return Response.success(usersRes);
default:
return Response.badRequest("Unknown object.");
}
} catch (e) {
return Response.error(e);
}
}
}

View File

@@ -1,25 +1,25 @@
import * as program from 'commander';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { I18nService } from 'jslib/abstractions/i18n.service';
import { SyncService } from "../services/sync.service";
import { SyncService } from '../services/sync.service';
import { Response } from 'jslib/cli/models/response';
import { 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) { }
constructor(private syncService: SyncService, private i18nService: I18nService) {}
async run(cmd: program.Command): Promise<Response> {
try {
const result = await this.syncService.sync(false, false);
const groupCount = result[0] != null ? result[0].length : 0;
const userCount = result[1] != null ? result[1].length : 0;
const res = new MessageResponse(this.i18nService.t('syncingComplete'),
this.i18nService.t('syncCounts', groupCount.toString(), userCount.toString()));
return Response.success(res);
} catch (e) {
return Response.error(e);
}
async run(): Promise<Response> {
try {
const result = await this.syncService.sync(false, false);
const groupCount = result[0] != null ? result[0].length : 0;
const userCount = result[1] != null ? result[1].length : 0;
const res = new MessageResponse(
this.i18nService.t("syncingComplete"),
this.i18nService.t("syncCounts", groupCount.toString(), userCount.toString())
);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
}
}

View File

@@ -1,24 +1,28 @@
import * as program from 'commander';
import * as program from "commander";
import { I18nService } from 'jslib/abstractions/i18n.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { SyncService } from '../services/sync.service';
import { SyncService } from "../services/sync.service";
import { ConnectorUtils } from '../utils';
import { ConnectorUtils } from "../utils";
import { Response } from 'jslib/cli/models/response';
import { TestResponse } from '../models/response/testResponse';
import { Response } from "jslib-node/cli/models/response";
import { TestResponse } from "../models/response/testResponse";
export class TestCommand {
constructor(private syncService: SyncService, private i18nService: I18nService) { }
constructor(private syncService: SyncService, private i18nService: I18nService) {}
async run(cmd: program.Command): Promise<Response> {
try {
const result = await ConnectorUtils.simulate(this.syncService, this.i18nService, cmd.last || false);
const res = new TestResponse(result);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
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);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
}
}

View File

@@ -1,6 +1,7 @@
export enum DirectoryType {
Ldap = 0,
AzureActiveDirectory = 1,
GSuite = 2,
Okta = 3,
Ldap = 0,
AzureActiveDirectory = 1,
GSuite = 2,
Okta = 3,
OneLogin = 4,
}

2
src/global.d.ts vendored
View File

@@ -1,3 +1,3 @@
declare function escape(s: string): string;
declare function unescape(s: string): string;
declare module 'duo_web_sdk';
declare module "duo_web_sdk";

View File

@@ -1,16 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline';
img-src 'self' data: *; child-src *; frame-src *; connect-src *;">
<meta name="viewport" content="width=device-width, initial-scale=1">
<head>
<meta charset="UTF-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; style-src 'self' 'unsafe-inline';
img-src 'self' data: *; child-src *; frame-src *; connect-src *;"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Bitwarden Directory Connector</title>
<base href="">
</head>
<body>
<base href="" />
</head>
<body>
<app-root>
<div id="loading"><i class="fa fa-spinner fa-spin fa-3x"></i></div>
<div id="loading"><i class="bwi bwi-spinner bwi-spin bwi-3x"></i></div>
</app-root>
</body>
</body>
</html>

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."
},
@@ -129,7 +147,7 @@
"message": "Self-hosted Environment"
},
"selfHostedEnvironmentFooter": {
"message": "Specify the base URL of your on-premise hosted Bitwarden installation."
"message": "Specify the base URL of your on-premises hosted Bitwarden installation."
},
"customEnvironment": {
"message": "Custom Environment"
@@ -140,6 +158,9 @@
"baseUrl": {
"message": "Server URL"
},
"webVaultUrl": {
"message": "Web Vault Server URL"
},
"apiUrl": {
"message": "API Server URL"
},
@@ -333,6 +354,9 @@
"rootPath": {
"message": "Root Path"
},
"identityAuthority": {
"message": "Identity Authority"
},
"tenant": {
"message": "Tenant"
},
@@ -399,6 +423,12 @@
"groupFilter": {
"message": "Group Filter"
},
"syncGroupsOneLogin": {
"message": "Sync roles"
},
"groupFilterOneLogin": {
"message": "Role Filter"
},
"groupObjectClass": {
"message": "Group Object Class"
},
@@ -414,8 +444,33 @@
"sync": {
"message": "Sync"
},
"duplicateEmails": {
"message": "Emails must be unique. Multiple entries pulled with the following emails:",
"desription": "Error message displayed when duplicate email addresses are synced. Followed by a list of duplicate emails."
},
"andMore": {
"message": "and $NUMBER$ more...",
"placeholders": {
"NUMBER": {
"content": "$1",
"example": "10"
}
}
},
"ldapEncrypted": {
"message": "This server uses an encrypted connection"
},
"ldapTls": {
"message": "Use TLS (STARTTLS)"
},
"ldapTlsCa": {
"message": "Certificate CA Chain (PEM)"
},
"ldapSsl": {
"message": "This server uses SSL (LDAPS)"
"message": "Use SSL (LDAPS)"
},
"ldapTlsUntrustedDesc": {
"message": "If your LDAP server uses a self-signed certificate for STARTTLS, you can configure certificate options below."
},
"ldapSslUntrustedDesc": {
"message": "If your LDAPS server uses an untrusted certificate you can configure certificate options below."
@@ -429,12 +484,15 @@
"ldapSslKey": {
"message": "Certificate Private Key (PEM)"
},
"ldapSslAllowUnauthorized": {
"message": "Allow untrusted SSL connections (not recommended)."
"ldapCertDoNotVerify": {
"message": "Do not verify server certificates (not recommended)."
},
"ldapAd": {
"message": "This server uses Active Directory"
},
"ldapPagedResults": {
"message": "This server pages search results"
},
"select": {
"message": "Select"
},
@@ -551,7 +609,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."
@@ -569,6 +627,13 @@
"hideToTray": {
"message": "Hide to Tray"
},
"alwaysOnTop": {
"message": "Always on Top",
"description": "Application window should always stay on top of other windows"
},
"hideToMenuBar": {
"message": "Hide to Menu Bar"
},
"savedSetting": {
"message": "Saved setting `$SETTING_NAME$`.",
"placeholders": {
@@ -577,5 +642,128 @@
"example": "server"
}
}
},
"largeImport": {
"message": "More than 2000 users or groups are expected to sync."
},
"overwriteExisting": {
"message": "Overwrite existing organization users based on current sync settings."
},
"clientId": {
"message": "Client ID"
},
"clientSecret": {
"message": "Client Secret"
},
"region": {
"message": "Region"
},
"enterpriseSingleSignOn": {
"message": "Enterprise Single Sign-On"
},
"setMasterPassword": {
"message": "Set Master Password"
},
"ssoCompleteRegistration": {
"message": "In order to complete logging in with SSO, please set a master password to access and protect your vault."
},
"newMasterPass": {
"message": "New Master Password"
},
"confirmNewMasterPass": {
"message": "Confirm New Master Password"
},
"masterPasswordPolicyInEffect": {
"message": "One or more organization policies require your master password to meet the following requirements:"
},
"policyInEffectMinComplexity": {
"message": "Minimum complexity score of $SCORE$",
"placeholders": {
"score": {
"content": "$1",
"example": "4"
}
}
},
"policyInEffectMinLength": {
"message": "Minimum length of $LENGTH$",
"placeholders": {
"length": {
"content": "$1",
"example": "14"
}
}
},
"policyInEffectUppercase": {
"message": "Contain one or more uppercase characters"
},
"policyInEffectLowercase": {
"message": "Contain one or more lowercase characters"
},
"policyInEffectNumbers": {
"message": "Contain one or more numbers"
},
"policyInEffectSpecial": {
"message": "Contain one or more of the following special characters $CHARS$",
"placeholders": {
"chars": {
"content": "$1",
"example": "!@#$%^&*"
}
}
},
"masterPassDesc": {
"message": "The master password is the password you use to access your vault. It is very important that you do not forget your master password. There is no way to recover the password in the event that you forget it."
},
"reTypeMasterPass": {
"message": "Re-type Master Password"
},
"masterPassHint": {
"message": "Master Password Hint (optional)"
},
"masterPassHintDesc": {
"message": "A master password hint can help you remember your password if you forget it."
},
"strong": {
"message": "Strong",
"description": "ex. A strong password. Scale: Weak -> Good -> Strong"
},
"good": {
"message": "Good",
"description": "ex. A good password. Scale: Weak -> Good -> Strong"
},
"weak": {
"message": "Weak",
"description": "ex. A weak password. Scale: Weak -> Good -> Strong"
},
"weakMasterPassword": {
"message": "Weak Master Password"
},
"weakMasterPasswordDesc": {
"message": "The master password you have chosen is weak. You should use a strong master password (or a passphrase) to properly protect your Bitwarden account. Are you sure you want to use this master password?"
},
"errorOccurred": {
"message": "An error has occurred."
},
"error": {
"message": "Error"
},
"masterPassLength": {
"message": "Master password must be at least 8 characters long."
},
"masterPassDoesntMatch": {
"message": "Master password confirmation does not match."
},
"masterPasswordPolicyRequirementsNotMet": {
"message": "Your new master password does not meet the policy requirements."
},
"loading": {
"message": "Loading"
},
"setMasterPasswordRedirect": {
"message": "In order to log in with SSO from the Directory Connector, you must first log in through the web vault to set your master password."
},
"launchWebVault": {
"message": "Launch Web Vault"
}
}

View File

@@ -1,88 +1,155 @@
import { app } from 'electron';
import * as path from 'path';
import { app } from "electron";
import * as path from "path";
import { MenuMain } from './main/menu.main';
import { MessagingMain } from './main/messaging.main';
import { I18nService } from './services/i18n.service';
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";
import { StateService } from "./services/state.service";
import { Account } from "./models/account";
import { StateFactory } from "jslib-common/factories/stateFactory";
import { GlobalState } from "jslib-common/models/domain/globalState";
export class Main {
logService: ElectronLogService;
i18nService: I18nService;
storageService: ElectronStorageService;
messagingService: ElectronMainMessagingService;
keytarStorageListener: KeytarStorageListener;
logService: ElectronLogService;
i18nService: I18nService;
storageService: ElectronStorageService;
messagingService: ElectronMainMessagingService;
keytarStorageListener: KeytarStorageListener;
stateService: StateService;
windowMain: WindowMain;
messagingMain: MessagingMain;
menuMain: MenuMain;
updaterMain: UpdaterMain;
trayMain: TrayMain;
windowMain: WindowMain;
messagingMain: MessagingMain;
menuMain: MenuMain;
updaterMain: UpdaterMain;
trayMain: TrayMain;
constructor() {
// Set paths for portable builds
let appDataPath = null;
if (process.env.BITWARDEN_CONNECTOR_APPDATA_DIR != null) {
appDataPath = process.env.BITWARDEN_CONNECTOR_APPDATA_DIR;
} else if (process.platform === 'win32' && process.env.PORTABLE_EXECUTABLE_DIR != null) {
appDataPath = path.join(process.env.PORTABLE_EXECUTABLE_DIR, 'bitwarden-connector-appdata');
}
if (appDataPath != null) {
app.setPath('userData', appDataPath);
}
app.setPath('logs', path.join(app.getPath('userData'), 'logs'));
const args = process.argv.slice(1);
const watch = args.some((val) => val === '--watch');
if (watch) {
// tslint:disable-next-line
require('electron-reload')(__dirname, {});
}
this.logService = new ElectronLogService(null, app.getPath('userData'));
this.i18nService = new I18nService('en', './locales/');
this.storageService = new ElectronStorageService(app.getPath('userData'));
this.windowMain = new WindowMain(this.storageService, 800, 600);
this.menuMain = new MenuMain(this);
this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain, 'directory-connector', () => {
this.messagingService.send('checkingForUpdate');
}, () => {
this.messagingService.send('doneCheckingForUpdate');
}, () => {
this.messagingService.send('doneCheckingForUpdate');
}, '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.messagingMain.onMessage(message);
});
this.keytarStorageListener = new KeytarStorageListener('Bitwarden Directory Connector');
constructor() {
// Set paths for portable builds
let appDataPath = null;
if (process.env.BITWARDEN_CONNECTOR_APPDATA_DIR != null) {
appDataPath = process.env.BITWARDEN_CONNECTOR_APPDATA_DIR;
} else if (process.platform === "win32" && process.env.PORTABLE_EXECUTABLE_DIR != null) {
appDataPath = path.join(process.env.PORTABLE_EXECUTABLE_DIR, "bitwarden-connector-appdata");
}
bootstrap() {
this.keytarStorageListener.init();
this.windowMain.init().then(async () => {
await this.i18nService.init(app.getLocale());
this.menuMain.init();
this.messagingMain.init();
await this.updaterMain.init();
await this.trayMain.init(this.i18nService.t('bitwardenDirectoryConnector'));
}, (e: any) => {
// tslint:disable-next-line
console.error(e);
});
if (appDataPath != null) {
app.setPath("userData", appDataPath);
}
app.setPath("logs", path.join(app.getPath("userData"), "logs"));
const args = process.argv.slice(1);
const watch = args.some((val) => val === "--watch");
if (watch) {
// tslint:disable-next-line
require("electron-reload")(__dirname, {});
}
this.logService = new ElectronLogService(null, app.getPath("userData"));
this.i18nService = new I18nService("en", "./locales/");
this.storageService = new ElectronStorageService(app.getPath("userData"));
this.stateService = new StateService(
this.storageService,
null,
this.logService,
null,
true,
new StateFactory(GlobalState, Account)
);
this.windowMain = new WindowMain(
this.stateService,
this.logService,
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");
},
() => {
this.messagingService.send("doneCheckingForUpdate");
},
() => {
this.messagingService.send("doneCheckingForUpdate");
},
"bitwardenDirectoryConnector"
);
this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.stateService);
this.messagingMain = new MessagingMain(
this.windowMain,
this.menuMain,
this.updaterMain,
this.trayMain
);
this.messagingService = new ElectronMainMessagingService(this.windowMain, (message) => {
this.messagingMain.onMessage(message);
});
this.keytarStorageListener = new KeytarStorageListener("Bitwarden Directory Connector", null);
}
bootstrap() {
this.keytarStorageListener.init();
this.windowMain.init().then(
async () => {
await this.i18nService.init(app.getLocale());
this.menuMain.init();
this.messagingMain.init();
await this.updaterMain.init();
await this.trayMain.init(this.i18nService.t("bitwardenDirectoryConnector"));
if (!app.isDefaultProtocolClient("bwdc")) {
app.setAsDefaultProtocolClient("bwdc");
}
// Process protocol for macOS
app.on("open-url", (event, url) => {
event.preventDefault();
this.processDeepLink([url]);
});
},
(e: any) => {
// tslint:disable-next-line
console.error(e);
}
);
}
private processDeepLink(argv: string[]): void {
argv
.filter((s) => s.indexOf("bwdc://") === 0)
.forEach((s) => {
const url = new URL(s);
const code = url.searchParams.get("code");
const receivedState = url.searchParams.get("state");
if (code != null && receivedState != null) {
this.messagingService.send("ssoCallback", { code: code, state: receivedState });
}
});
}
}
const main = new Main();

View File

@@ -1,60 +1,69 @@
import {
Menu,
MenuItem,
MenuItemConstructorOptions,
} from 'electron';
import { Menu, MenuItem, MenuItemConstructorOptions } from "electron";
import { Main } from '../main';
import { Main } from "../main";
import { BaseMenu } from 'jslib/electron/baseMenu';
import { BaseMenu } from "jslib-electron/baseMenu";
export class MenuMain extends BaseMenu {
menu: Menu;
menu: Menu;
constructor(private main: Main) {
super(main.i18nService, main.windowMain);
constructor(private main: Main) {
super(main.i18nService, main.windowMain);
}
init() {
this.initProperties();
this.initContextMenu();
this.initApplicationMenu();
}
private initApplicationMenu() {
const template: MenuItemConstructorOptions[] = [
this.editMenuItemOptions,
{
label: this.i18nService.t("view"),
submenu: this.viewSubMenuItemOptions,
},
this.windowMenuItemOptions,
];
if (process.platform === "darwin") {
const firstMenuPart: MenuItemConstructorOptions[] = [
{
label: this.i18nService.t("aboutBitwarden"),
role: "about",
},
];
template.unshift({
label: this.main.i18nService.t("bitwardenDirectoryConnector"),
submenu: firstMenuPart.concat(this.macAppMenuItemOptions),
});
// Window menu
template[template.length - 1].submenu = this.macWindowSubmenuOptions;
}
init() {
this.initProperties();
this.initContextMenu();
this.initApplicationMenu();
}
(template[template.length - 1].submenu as MenuItemConstructorOptions[]).splice(
1,
0,
{
label: this.main.i18nService.t(
process.platform === "darwin" ? "hideToMenuBar" : "hideToTray"
),
click: () => this.main.messagingService.send("hideToTray"),
accelerator: "CmdOrCtrl+Shift+M",
},
{
type: "checkbox",
label: this.main.i18nService.t("alwaysOnTop"),
checked: this.windowMain.win.isAlwaysOnTop(),
click: () => this.main.windowMain.toggleAlwaysOnTop(),
accelerator: "CmdOrCtrl+Shift+T",
}
);
private initApplicationMenu() {
const template: MenuItemConstructorOptions[] = [
this.editMenuItemOptions,
{
label: this.i18nService.t('view'),
submenu: this.viewSubMenuItemOptions,
},
this.windowMenuItemOptions,
];
if (process.platform === 'darwin') {
const firstMenuPart: MenuItemConstructorOptions[] = [
{
label: this.i18nService.t('aboutBitwarden'),
role: 'about',
},
];
template.unshift({
label: this.main.i18nService.t('bitwardenDirectoryConnector'),
submenu: firstMenuPart.concat(this.macAppMenuItemOptions),
});
// Window menu
template[template.length - 1].submenu = this.macWindowSubmenuOptions;
}
(template[template.length - 1].submenu as MenuItemConstructorOptions[]).splice(1, 0, {
label: this.main.i18nService.t('hideToTray'),
click: () => this.main.messagingService.send('hideToTray'),
accelerator: 'CmdOrCtrl+Shift+M',
});
this.menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(this.menu);
}
this.menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(this.menu);
}
}

View File

@@ -1,67 +1,68 @@
import {
app,
ipcMain,
} from 'electron';
import { app, 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';
import { MenuMain } from "./menu.main";
const SyncCheckInterval = 60 * 1000; // 1 minute
export class MessagingMain {
private syncTimeout: NodeJS.Timer;
private syncTimeout: NodeJS.Timer;
constructor(private windowMain: WindowMain, private menuMain: MenuMain,
private updaterMain: UpdaterMain, private trayMain: TrayMain) { }
constructor(
private windowMain: WindowMain,
private menuMain: MenuMain,
private updaterMain: UpdaterMain,
private trayMain: TrayMain
) {}
init() {
ipcMain.on('messagingService', async (event: any, message: any) => this.onMessage(message));
}
init() {
ipcMain.on("messagingService", async (event: any, message: any) => this.onMessage(message));
}
onMessage(message: any) {
switch (message.command) {
case 'checkForUpdate':
this.updaterMain.checkForUpdate(true);
break;
case 'scheduleNextDirSync':
this.scheduleNextSync();
break;
case 'cancelDirSync':
this.windowMain.win.webContents.send('messagingService', {
command: 'syncScheduleStopped',
});
if (this.syncTimeout) {
global.clearTimeout(this.syncTimeout);
}
break;
case 'hideToTray':
this.trayMain.hideToTray();
break;
default:
break;
}
}
private scheduleNextSync() {
this.windowMain.win.webContents.send('messagingService', {
command: 'syncScheduleStarted',
onMessage(message: any) {
switch (message.command) {
case "checkForUpdate":
this.updaterMain.checkForUpdate(true);
break;
case "scheduleNextDirSync":
this.scheduleNextSync();
break;
case "cancelDirSync":
this.windowMain.win.webContents.send("messagingService", {
command: "syncScheduleStopped",
});
if (this.syncTimeout) {
global.clearTimeout(this.syncTimeout);
global.clearTimeout(this.syncTimeout);
}
this.syncTimeout = global.setTimeout(() => {
if (this.windowMain.win == null) {
return;
}
this.windowMain.win.webContents.send('messagingService', {
command: 'checkDirSync',
});
}, SyncCheckInterval);
break;
case "hideToTray":
this.trayMain.hideToTray();
break;
default:
break;
}
}
private scheduleNextSync() {
this.windowMain.win.webContents.send("messagingService", {
command: "syncScheduleStarted",
});
if (this.syncTimeout) {
global.clearTimeout(this.syncTimeout);
}
this.syncTimeout = global.setTimeout(() => {
if (this.windowMain.win == null) {
return;
}
this.windowMain.win.webContents.send("messagingService", {
command: "checkDirSync",
});
}, SyncCheckInterval);
}
}

View File

@@ -0,0 +1,2 @@
// tslint:disable-next-line
export interface IConfiguration {}

47
src/models/account.ts Normal file
View File

@@ -0,0 +1,47 @@
import { Account as BaseAccount } from "jslib-common/models/domain/account";
import { DirectoryType } from "src/enums/directoryType";
import { AzureConfiguration } from "./azureConfiguration";
import { GSuiteConfiguration } from "./gsuiteConfiguration";
import { LdapConfiguration } from "./ldapConfiguration";
import { OktaConfiguration } from "./oktaConfiguration";
import { OneLoginConfiguration } from "./oneLoginConfiguration";
import { SyncConfiguration } from "./syncConfiguration";
export class Account extends BaseAccount {
directoryConfigurations?: DirectoryConfigurations = new DirectoryConfigurations();
directorySettings: DirectorySettings = new DirectorySettings();
clientKeys: ClientKeys = new ClientKeys();
constructor(init: Partial<Account>) {
super(init);
this.directoryConfigurations = init?.directoryConfigurations ?? new DirectoryConfigurations();
this.directorySettings = init?.directorySettings ?? new DirectorySettings();
}
}
export class ClientKeys {
clientId: string;
clientSecret: string;
}
export class DirectoryConfigurations {
ldap: LdapConfiguration;
gsuite: GSuiteConfiguration;
azure: AzureConfiguration;
okta: OktaConfiguration;
oneLogin: OneLoginConfiguration;
}
export class DirectorySettings {
organizationId?: string;
sync?: SyncConfiguration;
directoryType?: DirectoryType;
userDelta?: string;
groupDelta?: string;
lastUserSync?: Date;
lastGroupSync?: Date;
lastSyncHash?: string;
syncingDir?: boolean;
}

View File

@@ -1,5 +1,8 @@
export class AzureConfiguration {
tenant: string;
applicationId: string;
key: string;
import { IConfiguration } from "./IConfiguration";
export class AzureConfiguration implements IConfiguration {
identityAuthority: string;
tenant: string;
applicationId: string;
key: string;
}

View File

@@ -1,8 +1,8 @@
export abstract class Entry {
referenceId: string;
externalId: string;
referenceId: string;
externalId: string;
get displayName(): string {
return this.referenceId;
}
get displayName(): string {
return this.referenceId;
}
}

View File

@@ -1,15 +1,15 @@
import { Entry } from './entry';
import { Entry } from "./entry";
export class GroupEntry extends Entry {
name: string;
userMemberExternalIds = new Set<string>();
groupMemberReferenceIds = new Set<string>();
name: string;
userMemberExternalIds = new Set<string>();
groupMemberReferenceIds = new Set<string>();
get displayName(): string {
if (this.name == null) {
return this.referenceId;
}
return this.name;
get displayName(): string {
if (this.name == null) {
return this.referenceId;
}
return this.name;
}
}

View File

@@ -1,7 +1,9 @@
export class GSuiteConfiguration {
clientEmail: string;
privateKey: string;
domain: string;
adminUser: string;
customer: string;
import { IConfiguration } from "./IConfiguration";
export class GSuiteConfiguration implements IConfiguration {
clientEmail: string;
privateKey: string;
domain: string;
adminUser: string;
customer: string;
}

View File

@@ -1,15 +1,20 @@
export class LdapConfiguration {
ssl = false;
sslAllowUnauthorized = false;
sslCertPath: string;
sslKeyPath: string;
sslCaPath: string;
hostname: string;
port = 389;
domain: string;
rootPath: string;
currentUser = false;
username: string;
password: string;
ad = true;
import { IConfiguration } from "./IConfiguration";
export class LdapConfiguration implements IConfiguration {
ssl = false;
startTls = false;
tlsCaPath: string;
sslAllowUnauthorized = false;
sslCertPath: string;
sslKeyPath: string;
sslCaPath: string;
hostname: string;
port = 389;
domain: string;
rootPath: string;
currentUser = false;
username: string;
password: string;
ad = true;
pagedSearch = true;
}

View File

@@ -1,4 +1,6 @@
export class OktaConfiguration {
orgUrl: string;
token: string;
import { IConfiguration } from "./IConfiguration";
export class OktaConfiguration implements IConfiguration {
orgUrl: string;
token: string;
}

View File

@@ -0,0 +1,7 @@
import { IConfiguration } from "./IConfiguration";
export class OneLoginConfiguration implements IConfiguration {
clientId: string;
clientSecret: string;
region = "us";
}

View File

@@ -1,13 +1,13 @@
import { GroupEntry } from '../groupEntry';
import { GroupEntry } from "../groupEntry";
export class GroupResponse {
externalId: string;
displayName: string;
userIds: string[];
externalId: string;
displayName: string;
userIds: string[];
constructor(g: GroupEntry) {
this.externalId = g.externalId;
this.displayName = g.displayName;
this.userIds = Array.from(g.userMemberExternalIds);
}
constructor(g: GroupEntry) {
this.externalId = g.externalId;
this.displayName = g.displayName;
this.userIds = Array.from(g.userMemberExternalIds);
}
}

View File

@@ -1,22 +1,25 @@
import { GroupResponse } from './groupResponse';
import { UserResponse } from './userResponse';
import { GroupResponse } from "./groupResponse";
import { UserResponse } from "./userResponse";
import { SimResult } from '../simResult';
import { SimResult } from "../simResult";
import { BaseResponse } from 'jslib/cli/models/response/baseResponse';
import { BaseResponse } from "jslib-node/cli/models/response/baseResponse";
export class TestResponse implements BaseResponse {
object: string;
groups: GroupResponse[] = [];
enabledUsers: UserResponse[] = [];
disabledUsers: UserResponse[] = [];
deletedUsers: UserResponse[] = [];
object: string;
groups: GroupResponse[] = [];
enabledUsers: UserResponse[] = [];
disabledUsers: UserResponse[] = [];
deletedUsers: UserResponse[] = [];
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)) : [];
}
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)) : [];
}
}

View File

@@ -1,11 +1,11 @@
import { UserEntry } from '../userEntry';
import { UserEntry } from "../userEntry";
export class UserResponse {
externalId: string;
displayName: string;
externalId: string;
displayName: string;
constructor(u: UserEntry) {
this.externalId = u.externalId;
this.displayName = u.displayName;
}
constructor(u: UserEntry) {
this.externalId = u.externalId;
this.displayName = u.displayName;
}
}

View File

@@ -1,10 +1,10 @@
import { GroupEntry } from './groupEntry';
import { UserEntry } from './userEntry';
import { GroupEntry } from "./groupEntry";
import { UserEntry } from "./userEntry";
export class SimResult {
groups: GroupEntry[] = [];
users: UserEntry[] = [];
enabledUsers: UserEntry[] = [];
disabledUsers: UserEntry[] = [];
deletedUsers: UserEntry[] = [];
groups: GroupEntry[] = [];
users: UserEntry[] = [];
enabledUsers: UserEntry[] = [];
disabledUsers: UserEntry[] = [];
deletedUsers: UserEntry[] = [];
}

View File

@@ -1,21 +1,23 @@
export class SyncConfiguration {
users = false;
groups = false;
interval = 5;
userFilter: string;
groupFilter: string;
removeDisabled = false;
// Ldap properties
groupObjectClass: string;
userObjectClass: string;
groupPath: string;
userPath: string;
groupNameAttribute: string;
userEmailAttribute: string;
memberAttribute: string;
useEmailPrefixSuffix = false;
emailPrefixAttribute: string;
emailSuffix: string;
creationDateAttribute: string;
revisionDateAttribute: string;
users = false;
groups = false;
interval = 5;
userFilter: string;
groupFilter: string;
removeDisabled = false;
overwriteExisting = false;
largeImport = false;
// Ldap properties
groupObjectClass: string;
userObjectClass: string;
groupPath: string;
userPath: string;
groupNameAttribute: string;
userEmailAttribute: string;
memberAttribute: string;
useEmailPrefixSuffix = false;
emailPrefixAttribute: string;
emailSuffix: string;
creationDateAttribute: string;
revisionDateAttribute: string;
}

View File

@@ -1,15 +1,15 @@
import { Entry } from './entry';
import { Entry } from "./entry";
export class UserEntry extends Entry {
email: string;
disabled = false;
deleted = false;
email: string;
disabled = false;
deleted = false;
get displayName(): string {
if (this.email == null) {
return this.referenceId;
}
return this.email;
get displayName(): string {
if (this.email == null) {
return this.referenceId;
}
return this.email;
}
}

1757
src/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
{
"name": "bitwarden-directory-connector",
"name": "@bitwarden/directory-connector",
"productName": "Bitwarden Directory Connector",
"description": "Sync your user directory to your Bitwarden organization.",
"version": "2.5.1",
"author": "8bit Solutions LLC <hello@bitwarden.com> (https://bitwarden.com)",
"version": "2.9.9",
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
"homepage": "https://bitwarden.com",
"license": "GPL-3.0",
"main": "main.js",
@@ -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.0.6",
"keytar": "4.4.1"
"browser-hrtime": "^1.1.8",
"electron-log": "4.4.1",
"electron-store": "8.0.1",
"electron-updater": "4.6.1",
"keytar": "7.7.0"
}
}

View File

@@ -1,264 +1,321 @@
import * as chk from 'chalk';
import * as program from 'commander';
import * as path from 'path';
import * as chalk from "chalk";
import * as program from "commander";
import * as path from "path";
import { Main } from './bwdc';
import { Main } from "./bwdc";
import { ClearCacheCommand } from './commands/clearCache.command';
import { ConfigCommand } from './commands/config.command';
import { LastSyncCommand } from './commands/lastSync.command';
import { SyncCommand } from './commands/sync.command';
import { TestCommand } from './commands/test.command';
import { ClearCacheCommand } from "./commands/clearCache.command";
import { ConfigCommand } from "./commands/config.command";
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 { Response } from "jslib-node/cli/models/response";
import { StringResponse } from "jslib-node/cli/models/response/stringResponse";
const chalk = chk.default;
const writeLn = (s: string, finalLine: boolean = false) => {
if (finalLine && process.platform === 'win32') {
process.stdout.write(s);
} else {
process.stdout.write(s + '\n');
}
import { Utils } from "jslib-common/misc/utils";
const writeLn = (s: string, finalLine: boolean = false, error: boolean = false) => {
const stream = error ? process.stderr : process.stdout;
if (finalLine && process.platform === "win32") {
stream.write(s);
} else {
stream.write(s + "\n");
}
};
export class Program extends BaseProgram {
constructor(private main: Main) {
super(main.userService, writeLn);
}
constructor(private main: Main) {
super(main.stateService, writeLn);
}
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('--quiet', 'Don\'t return anything to stdout.')
.version(this.main.platformUtilsService.getApplicationVersion(), '-v, --version');
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(await this.main.platformUtilsService.getApplicationVersion(), "-v, --version");
program.on('option:pretty', () => {
process.env.BW_PRETTY = 'true';
});
program.on("option:pretty", () => {
process.env.BW_PRETTY = "true";
});
program.on('option:raw', () => {
process.env.BW_RAW = 'true';
});
program.on("option:raw", () => {
process.env.BW_RAW = "true";
});
program.on('option:quiet', () => {
process.env.BW_QUIET = 'true';
});
program.on("option:quiet", () => {
process.env.BW_QUIET = "true";
});
program.on('option:response', () => {
process.env.BW_RESPONSE = 'true';
});
program.on("option:response", () => {
process.env.BW_RESPONSE = "true";
});
program.on('command:*', () => {
writeLn(chalk.redBright('Invalid command: ' + program.args.join(' ')));
writeLn('See --help for a list of available commands.', true);
process.exitCode = 1;
});
program.on("option:cleanexit", () => {
process.env.BW_CLEANEXIT = "true";
});
program.on('--help', () => {
writeLn('\n Examples:');
writeLn('');
writeLn(' bwdc login');
writeLn(' bwdc test');
writeLn(' bwdc sync');
writeLn(' bwdc last-sync');
writeLn(' bwdc config server https://bw.company.com');
writeLn(' bwdc update');
writeLn('', true);
});
program.on("option:nointeraction", () => {
process.env.BW_NOINTERACTION = "true";
});
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.')
.on('--help', () => {
writeLn('\n Notes:');
writeLn('');
writeLn(' See docs for valid `method` enum values.');
writeLn('');
writeLn(' Examples:');
writeLn('');
writeLn(' bw login');
writeLn(' bw login john@example.com myPassword321');
writeLn(' bw login john@example.com myPassword321 --method 1 --code 249213');
writeLn('', true);
})
.action(async (email: string, password: string, cmd: program.Command) => {
await this.exitIfAuthed();
const command = new LoginCommand(this.main.authService, this.main.apiService, this.main.i18nService);
const response = await command.run(email, password, cmd);
this.processResponse(response);
});
program.on("command:*", () => {
writeLn(chalk.redBright("Invalid command: " + program.args.join(" ")), false, true);
writeLn("See --help for a list of available commands.", true, true);
process.exitCode = 1;
});
program
.command('logout')
.description('Log out of the current user account.')
.on('--help', () => {
writeLn('\n Examples:');
writeLn('');
writeLn(' bw logout');
writeLn('', true);
})
.action(async (cmd) => {
await this.exitIfNotAuthed();
const command = new LogoutCommand(this.main.authService, this.main.i18nService,
async () => await this.main.logout());
const response = await command.run(cmd);
this.processResponse(response);
});
program.on("--help", () => {
writeLn("\n Examples:");
writeLn("");
writeLn(" bwdc login");
writeLn(" bwdc test");
writeLn(" bwdc sync");
writeLn(" bwdc last-sync");
writeLn(" bwdc config server https://bw.company.com");
writeLn(" bwdc update");
writeLn("", true);
});
program
.command('test')
.description('Test a simulated sync.')
.option('-l, --last', 'Since the last successful sync.')
.on('--help', () => {
writeLn('\n Examples:');
writeLn('');
writeLn(' bwdc test');
writeLn(' bwdc test --last');
writeLn('', true);
})
.action(async (cmd) => {
await this.exitIfNotAuthed();
const command = new TestCommand(this.main.syncService, this.main.i18nService);
const response = await command.run(cmd);
this.processResponse(response);
});
program
.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 (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,
this.main.stateService,
this.main.cryptoService,
this.main.policyService,
"connector"
);
program
.command('sync')
.description('Sync the directory.')
.on('--help', () => {
writeLn('\n Examples:');
writeLn('');
writeLn(' bwdc sync');
writeLn('', true);
})
.action(async (cmd) => {
await this.exitIfNotAuthed();
const command = new SyncCommand(this.main.syncService, this.main.i18nService);
const response = await command.run(cmd);
this.processResponse(response);
});
program
.command('last-sync <object>')
.description('Get the last successful sync date.')
.on('--help', () => {
writeLn('\n Notes:');
writeLn('');
writeLn(' Returns empty response if no sync has been performed for the given object.');
writeLn('');
writeLn(' Examples:');
writeLn('');
writeLn(' bwdc last-sync groups');
writeLn(' bwdc last-sync users');
writeLn('', true);
})
.action(async (object: string, cmd: program.Command) => {
await this.exitIfNotAuthed();
const command = new LastSyncCommand(this.main.configurationService);
const response = await command.run(object, cmd);
this.processResponse(response);
});
program
.command('config <setting> <value>')
.description('Configure settings.')
.on('--help', () => {
writeLn('\n Settings:');
writeLn('');
writeLn(' server - On-premise hosted installation URL.');
writeLn(' directory - The type of directory to use.');
writeLn(' ldap.password - The password for connection to this LDAP server.');
writeLn(' azure.key - The Azure AD secret key.');
writeLn(' gsuite.key - The G Suite private key.');
writeLn(' okta.token - The Okta token.');
writeLn('');
writeLn(' Examples:');
writeLn('');
writeLn(' bwdc config server https://bw.company.com');
writeLn(' bwdc config server bitwarden.com');
writeLn(' bwdc config directory 1');
writeLn(' bwdc config ldap.password <password>');
writeLn(' bwdc config azure.key <key>');
writeLn(' bwdc config gsuite.key <key>');
writeLn(' bwdc config okta.token <token>');
writeLn('', true);
})
.action(async (setting, value, cmd) => {
const command = new ConfigCommand(this.main.environmentService, this.main.i18nService,
this.main.configurationService);
const response = await command.run(setting, value, cmd);
this.processResponse(response);
});
program
.command('data-file')
.description('Path to data.json database file.')
.on('--help', () => {
writeLn('\n Examples:');
writeLn('');
writeLn(' bwdc data-file');
writeLn('', true);
})
.action(() => {
this.processResponse(
Response.success(new StringResponse(path.join(this.main.dataFilePath, 'data.json'))));
});
program
.command('clear-cache')
.description('Clear the sync cache.')
.on('--help', () => {
writeLn('\n Examples:');
writeLn('');
writeLn(' bwdc clear-cache');
writeLn('', true);
})
.action(async (cmd) => {
const command = new ClearCacheCommand(this.main.configurationService, this.main.i18nService);
const response = await command.run(cmd);
this.processResponse(response);
});
program
.command('update')
.description('Check for updates.')
.on('--help', () => {
writeLn('\n Notes:');
writeLn('');
writeLn(' Returns the URL to download the newest version of this CLI tool.');
writeLn('');
writeLn(' Use the `--raw` option to return only the download URL for the update.');
writeLn('');
writeLn(' Examples:');
writeLn('');
writeLn(' bwdc update');
writeLn(' bwdc update --raw');
writeLn('', true);
})
.action(async (cmd) => {
const command = new UpdateCommand(this.main.platformUtilsService, this.main.i18nService,
'directory-connector', 'bwdc', false);
const response = await command.run(cmd);
this.processResponse(response);
});
program
.parse(process.argv);
if (process.argv.slice(2).length === 0) {
program.outputHelp();
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);
});
program
.command("logout")
.description("Log out of the current user account.")
.on("--help", () => {
writeLn("\n Examples:");
writeLn("");
writeLn(" bwdc logout");
writeLn("", true);
})
.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();
this.processResponse(response);
});
program
.command("test")
.description("Test a simulated sync.")
.option("-l, --last", "Since the last successful sync.")
.on("--help", () => {
writeLn("\n Examples:");
writeLn("");
writeLn(" bwdc test");
writeLn(" bwdc test --last");
writeLn("", true);
})
.action(async (options: program.OptionValues) => {
await this.exitIfNotAuthed();
const command = new TestCommand(this.main.syncService, this.main.i18nService);
const response = await command.run(options);
this.processResponse(response);
});
program
.command("sync")
.description("Sync the directory.")
.on("--help", () => {
writeLn("\n Examples:");
writeLn("");
writeLn(" bwdc sync");
writeLn("", true);
})
.action(async () => {
await this.exitIfNotAuthed();
const command = new SyncCommand(this.main.syncService, this.main.i18nService);
const response = await command.run();
this.processResponse(response);
});
program
.command("last-sync <object>")
.description("Get the last successful sync date.")
.on("--help", () => {
writeLn("\n Notes:");
writeLn("");
writeLn(" Returns empty response if no sync has been performed for the given object.");
writeLn("");
writeLn(" Examples:");
writeLn("");
writeLn(" bwdc last-sync groups");
writeLn(" bwdc last-sync users");
writeLn("", true);
})
.action(async (object: string) => {
await this.exitIfNotAuthed();
const command = new LastSyncCommand(this.main.stateService);
const response = await command.run(object);
this.processResponse(response);
});
program
.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("");
writeLn(" server - On-premise hosted installation URL.");
writeLn(" directory - The type of directory to use.");
writeLn(" ldap.password - The password for connection to this LDAP server.");
writeLn(" azure.key - The Azure AD secret key.");
writeLn(" gsuite.key - The G Suite private key.");
writeLn(" okta.token - The Okta token.");
writeLn(" onelogin.secret - The OneLogin client secret.");
writeLn("");
writeLn(" Examples:");
writeLn("");
writeLn(" bwdc config server https://bw.company.com");
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: string, value: string, options: program.OptionValues) => {
const command = new ConfigCommand(
this.main.environmentService,
this.main.i18nService,
this.main.stateService
);
const response = await command.run(setting, value, options);
this.processResponse(response);
});
program
.command("data-file")
.description("Path to data.json database file.")
.on("--help", () => {
writeLn("\n Examples:");
writeLn("");
writeLn(" bwdc data-file");
writeLn("", true);
})
.action(() => {
this.processResponse(
Response.success(new StringResponse(path.join(this.main.dataFilePath, "data.json")))
);
});
program
.command("clear-cache")
.description("Clear the sync cache.")
.on("--help", () => {
writeLn("\n Examples:");
writeLn("");
writeLn(" bwdc clear-cache");
writeLn("", true);
})
.action(async (options: program.OptionValues) => {
const command = new ClearCacheCommand(this.main.i18nService, this.main.stateService);
const response = await command.run(options);
this.processResponse(response);
});
program
.command("update")
.description("Check for updates.")
.on("--help", () => {
writeLn("\n Notes:");
writeLn("");
writeLn(" Returns the URL to download the newest version of this CLI tool.");
writeLn("");
writeLn(" Use the `--raw` option to return only the download URL for the update.");
writeLn("");
writeLn(" Examples:");
writeLn("");
writeLn(" bwdc update");
writeLn(" bwdc update --raw");
writeLn("", true);
})
.action(async () => {
const command = new UpdateCommand(
this.main.platformUtilsService,
this.main.i18nService,
"directory-connector",
"bwdc",
false
);
const response = await command.run();
this.processResponse(response);
});
program.parse(process.argv);
if (process.argv.slice(2).length === 0) {
program.outputHelp();
}
}
async exitIfAuthed() {
const authed = await this.stateService.getIsAuthenticated();
if (authed) {
const type = await this.stateService.getEntityType();
const id = await this.stateService.getEntityId();
this.processResponse(
Response.error("You are already logged in as " + type + "." + id + "."),
true
);
}
}
async exitIfNotAuthed() {
const authed = await this.stateService.getIsAuthenticated();
if (!authed) {
this.processResponse(Response.error("You are not logged in."), true);
}
}
}

View File

@@ -1,5 +1,15 @@
$theme-colors: ( "primary": #3c8dbc, "primary-accent": #286090, "danger": #dd4b39, "success": #00a65a, "info": #555555, "warning": #bf7e16);
$font-family-sans-serif: 'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
$theme-colors: (
"primary": #175ddc,
"primary-accent": #1252a3,
"danger": #dd4b39,
"success": #00a65a,
"info": #555555,
"warning": #bf7e16,
"secondary": #ced4da,
"secondary-alt": #1a3b66,
);
$font-family-sans-serif: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
$h1-font-size: 2rem;
$h2-font-size: 1.3rem;
@@ -8,4 +18,13 @@ $h4-font-size: 1rem;
$h5-font-size: 1rem;
$h6-font-size: 1rem;
$primary: map_get($theme-colors, "primary");
$primary-accent: map_get($theme-colors, "primary-accent");
$success: map_get($theme-colors, "success");
$info: map_get($theme-colors, "info");
$warning: map_get($theme-colors, "warning");
$danger: map_get($theme-colors, "danger");
$secondary: map_get($theme-colors, "secondary");
$secondary-alt: map_get($theme-colors, "secondary-alt");
@import "~bootstrap/scss/bootstrap.scss";

View File

@@ -1,7 +1,7 @@
@import "~bootstrap/scss/_variables.scss";
html.os_windows {
body {
border-top: 1px solid $gray-400;
}
body {
border-top: 1px solid $gray-400;
}
}

View File

@@ -1,55 +1,143 @@
@import "~bootstrap/scss/_variables.scss";
body {
padding: 10px 0 20px 0;
padding: 10px 0 20px 0;
}
h1 {
border-bottom: 1px solid $border-color;
margin-bottom: 20px;
border-bottom: 1px solid $border-color;
margin-bottom: 20px;
small {
color: $text-muted;
font-size: $h1-font-size * .5;
}
small {
color: $text-muted;
font-size: $h1-font-size * 0.5;
}
}
h2 {
text-transform: uppercase;
font-weight: bold;
text-transform: uppercase;
font-weight: bold;
}
h3 {
text-transform: uppercase;
text-transform: uppercase;
}
h4 {
font-weight: bold;
font-weight: bold;
}
#duo-frame {
background: url('../images/loading.svg') 0 0 no-repeat;
height: 380px;
background: url("../images/loading.svg") 0 0 no-repeat;
height: 380px;
iframe {
width: 100%;
height: 100%;
border: none;
}
iframe {
width: 100%;
height: 100%;
border: none;
}
}
app-root > #loading {
text-align: center;
margin-top: 20px;
color: $text-muted;
text-align: center;
margin-top: 20px;
color: $text-muted;
}
ul.testing-list {
ul {
padding-left: 18px;
}
ul {
padding-left: 18px;
}
li.deleted {
text-decoration: line-through;
}
li.deleted {
text-decoration: line-through;
}
}
.callout {
padding: 10px;
margin-bottom: 10px;
border: 1px solid #000000;
border-left-width: 5px;
border-radius: 3px;
border-color: #ddd;
background-color: white;
.callout-heading {
margin-top: 0;
}
h3.callout-heading {
font-weight: bold;
text-transform: uppercase;
}
&.callout-primary {
border-left-color: $primary;
.callout-heading {
color: $primary;
}
}
&.callout-info {
border-left-color: $info;
.callout-heading {
color: $info;
}
}
&.callout-danger {
border-left-color: $danger;
.callout-heading {
color: $danger;
}
}
&.callout-success {
border-left-color: $success;
.callout-heading {
color: $success;
}
}
&.callout-warning {
border-left-color: $warning;
.callout-heading {
color: $warning;
}
}
ul {
padding-left: 40px;
margin: 0;
}
}
.btn[class*="btn-outline-"] {
&:not(:hover) {
border-color: $secondary;
background-color: #fbfbfb;
}
}
.btn-outline-secondary {
color: $text-muted;
&:hover:not(:disabled) {
color: $body-color;
}
&:disabled {
opacity: 1;
}
&:focus,
&.focus {
box-shadow: 0 0 0 $btn-focus-width rgba(mix(color-yiq($primary), $primary, 15%), 0.5);
}
}

View File

@@ -1,130 +1,126 @@
$fa-font-path: "~font-awesome/fonts";
@import "~font-awesome/scss/font-awesome.scss";
@import "~angular2-toaster/toaster";
@import "~ngx-toastr/toastr";
@import "~bootstrap/scss/_variables.scss";
#toast-container {
.toast-container {
.toast-close-button {
font-size: 18px;
margin-right: 4px;
}
.ngx-toastr {
align-items: center;
background-image: none !important;
border-radius: $border-radius;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.35);
display: flex;
padding: 15px;
.toast-close-button {
right: -0.15em;
position: absolute;
right: 5px;
top: 0;
}
.toast {
opacity: 1 !important;
background-image: none !important;
border-radius: $border-radius;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.35);
display: flex;
align-items: center;
&:hover {
box-shadow: 0 0 10px rgba(0, 0, 0, 0.6);
}
&:before {
font-family: FontAwesome;
font-size: 25px;
line-height: 20px;
float: left;
color: #ffffff;
padding-right: 10px;
margin: auto 0 auto -36px;
}
.toaster-icon {
display: none;
}
.toast-message {
p {
margin-bottom: 0.5rem;
&:last-child {
margin-bottom: 0;
}
}
}
&.toast-danger, &.toast-error {
background-image: none !important;
background-color: $danger;
&:before {
content: "\f0e7";
margin-left: -30px;
}
}
&.toast-warning {
background-image: none !important;
background-color: $warning;
&:before {
content: "\f071";
}
}
&.toast-info {
background-image: none !important;
background-color: $info;
&:before {
content: "\f05a";
}
}
&.toast-success {
background-image: none !important;
background-color: $success;
&:before {
content: "\f00C";
}
}
&:hover {
box-shadow: 0 0 10px rgba(0, 0, 0, 0.6);
}
.icon i::before {
float: left;
font-style: normal;
font-family: $icomoon-font-family;
font-size: 25px;
line-height: 20px;
padding-right: 15px;
}
.toast-message {
p {
margin-bottom: 0.5rem;
&:last-child {
margin-bottom: 0;
}
}
}
&.toast-danger,
&.toast-error {
background-color: $danger;
.icon i::before {
content: map_get($icons, "error");
}
}
&.toast-warning {
background-color: $warning;
.icon i::before {
content: map_get($icons, "exclamation-triangle");
}
}
&.toast-info {
background-color: $info;
.icon i:before {
content: map_get($icons, "info-circle");
}
}
&.toast-success {
background-color: $success;
.icon i:before {
content: map_get($icons, "check");
}
}
}
}
@keyframes modalshow {
0% {
opacity: 0;
transform: translate(0, -25%);
}
0% {
opacity: 0;
transform: translate(0, -25%);
}
100% {
opacity: 1;
transform: translate(0, 0);
}
100% {
opacity: 1;
transform: translate(0, 0);
}
}
@keyframes backdropshow {
0% {
opacity: 0;
}
0% {
opacity: 0;
}
100% {
opacity: $modal-backdrop-opacity;
}
100% {
opacity: $modal-backdrop-opacity;
}
}
.modal {
display: block !important;
opacity: 1 !important;
display: block !important;
opacity: 1 !important;
}
.modal-dialog {
.modal.fade & {
transform: initial !important;
animation: modalshow 0.3s ease-in;
}
.modal.show & {
transform: initial !important;
}
transform: translate(0, 0);
.modal.fade & {
transform: initial !important;
animation: modalshow 0.3s ease-in;
}
.modal.show & {
transform: initial !important;
}
transform: translate(0, 0);
}
.modal-backdrop {
&.fade {
animation: backdropshow 0.1s ease-in;
}
opacity: $modal-backdrop-opacity !important;
&.fade {
animation: backdropshow 0.1s ease-in;
}
opacity: $modal-backdrop-opacity !important;
}

View File

@@ -1,4 +1,5 @@
@import "../css/webfonts.css";
@import "../../jslib/angular/src/scss/webfonts.css";
@import "../../jslib/angular/src/scss/bwicons/styles/style.scss";
@import "bootstrap.scss";
@import "pages.scss";
@import "misc.scss";

View File

@@ -0,0 +1,36 @@
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 { StateService } from "../abstractions/state.service";
import { ApiService as ApiServiceBase } from "jslib-common/services/api.service";
export async function refreshToken(stateService: StateService, authService: AuthService) {
try {
const clientId = await stateService.getApiKeyClientId();
const clientSecret = await stateService.getApiKeyClientSecret();
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,119 @@
import { ApiService } from "jslib-common/abstractions/api.service";
import { AppIdService } from "jslib-common/abstractions/appId.service";
import { CryptoService } from "jslib-common/abstractions/crypto.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 { KeyConnectorService } from "jslib-common/abstractions/keyConnector.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 { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { StateService } from "../abstractions/state.service";
import { AuthService as AuthServiceBase } from "jslib-common/services/auth.service";
import { Account, DirectoryConfigurations, DirectorySettings } from "src/models/account";
import { AccountKeys, AccountProfile, AccountTokens } from "jslib-common/models/domain/account";
import { AuthResult } from "jslib-common/models/domain/authResult";
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,
tokenService: TokenService,
appIdService: AppIdService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
messagingService: MessagingService,
vaultTimeoutService: VaultTimeoutService,
logService: LogService,
cryptoFunctionService: CryptoFunctionService,
environmentService: EnvironmentService,
keyConnectorService: KeyConnectorService,
stateService: StateService
) {
super(
cryptoService,
apiService,
tokenService,
appIdService,
i18nService,
platformUtilsService,
messagingService,
vaultTimeoutService,
logService,
cryptoFunctionService,
keyConnectorService,
environmentService,
stateService,
false
);
}
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);
}
private async organizationLogInHelper(clientId: string, clientSecret: string) {
const appId = await this.appIdService.getAppId();
const entityId = clientId.split("organization.")[1];
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.stateService.addAccount(
new Account({
profile: {
...new AccountProfile(),
...{
userId: entityId,
apiKeyClientId: clientId,
entityId: entityId,
},
},
tokens: {
...new AccountTokens(),
...{
accessToken: tokenResponse.accessToken,
refreshToken: tokenResponse.refreshToken,
},
},
keys: {
...new AccountKeys(),
...{
apiKeyClientSecret: clientSecret,
},
},
directorySettings: new DirectorySettings(),
directoryConfigurations: new DirectoryConfigurations(),
})
);
return result;
}
}

View File

@@ -1,340 +1,529 @@
import * as graph from '@microsoft/microsoft-graph-client';
import * as graphType from '@microsoft/microsoft-graph-types';
import * as https from 'https';
import * as querystring from 'querystring';
import * as graph from "@microsoft/microsoft-graph-client";
import * as graphType from "@microsoft/microsoft-graph-types";
import * as https from "https";
import * as querystring from "querystring";
import { DirectoryType } from '../enums/directoryType';
import { DirectoryType } from "../enums/directoryType";
import { AzureConfiguration } from '../models/azureConfiguration';
import { GroupEntry } from '../models/groupEntry';
import { SyncConfiguration } from '../models/syncConfiguration';
import { UserEntry } from '../models/userEntry';
import { AzureConfiguration } from "../models/azureConfiguration";
import { GroupEntry } from "../models/groupEntry";
import { SyncConfiguration } from "../models/syncConfiguration";
import { UserEntry } from "../models/userEntry";
import { BaseDirectoryService } from './baseDirectory.service';
import { ConfigurationService } from './configuration.service';
import { DirectoryService } from './directory.service';
import { BaseDirectoryService } from "./baseDirectory.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 { StateService } from "../abstractions/state.service";
const NextLink = '@odata.nextLink';
const ObjectType = '@odata.type';
const AzurePublicIdentityAuhtority = "login.microsoftonline.com";
const AzureGovermentIdentityAuhtority = "login.microsoftonline.us";
const NextLink = "@odata.nextLink";
const DeltaLink = "@odata.deltaLink";
const ObjectType = "@odata.type";
const UserSelectParams = "?$select=id,mail,userPrincipalName,displayName,accountEnabled";
enum UserSetType {
IncludeUser,
ExcludeUser,
IncludeGroup,
ExcludeGroup,
IncludeUser,
ExcludeUser,
IncludeGroup,
ExcludeGroup,
}
export class AzureDirectoryService extends BaseDirectoryService implements DirectoryService {
private client: graph.Client;
private dirConfig: AzureConfiguration;
private syncConfig: SyncConfiguration;
private accessToken: string;
private accessTokenExpiration: Date;
export class AzureDirectoryService extends BaseDirectoryService implements IDirectoryService {
private client: graph.Client;
private dirConfig: AzureConfiguration;
private syncConfig: SyncConfiguration;
private accessToken: string;
private accessTokenExpiration: Date;
constructor(private configurationService: ConfigurationService, private logService: LogService,
private i18nService: I18nService) {
super();
this.init();
constructor(
private logService: LogService,
private i18nService: I18nService,
private stateService: StateService
) {
super();
this.init();
}
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
const type = await this.stateService.getDirectoryType();
if (type !== DirectoryType.AzureActiveDirectory) {
return;
}
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
const type = await this.configurationService.getDirectoryType();
if (type !== DirectoryType.AzureActiveDirectory) {
return;
}
this.dirConfig = await this.configurationService.getDirectory<AzureConfiguration>(
DirectoryType.AzureActiveDirectory);
if (this.dirConfig == null) {
return;
}
this.syncConfig = await this.configurationService.getSync();
if (this.syncConfig == null) {
return;
}
let users: UserEntry[];
if (this.syncConfig.users) {
users = await this.getUsers();
}
let groups: GroupEntry[];
if (this.syncConfig.groups) {
const setFilter = this.createCustomSet(this.syncConfig.groupFilter);
groups = await this.getGroups(setFilter);
users = this.filterUsersFromGroupsSet(users, groups, setFilter);
}
return [groups, users];
this.dirConfig = await this.stateService.getDirectory<AzureConfiguration>(
DirectoryType.AzureActiveDirectory
);
if (this.dirConfig == null) {
return;
}
private async getUsers(): Promise<UserEntry[]> {
const entryIds = new Set<string>();
const entries: UserEntry[] = [];
const userReq = this.client.api('/users');
let res = await userReq.get();
const setFilter = this.createCustomUserSet(this.syncConfig.userFilter);
while (true) {
const users: graphType.User[] = res.value;
if (users != null) {
for (const user of users) {
if (user.id == null || entryIds.has(user.id)) {
continue;
}
const entry = this.buildUser(user);
if (await this.filterOutUserResult(setFilter, entry)) {
continue;
}
if (!entry.disabled && !entry.deleted &&
(entry.email == null || entry.email.indexOf('#') > -1)) {
continue;
}
entries.push(entry);
entryIds.add(user.id);
}
}
if (res[NextLink] == null) {
break;
} else {
const nextReq = this.client.api(res[NextLink]);
res = await nextReq.get();
}
}
return entries;
this.syncConfig = await this.stateService.getSync();
if (this.syncConfig == null) {
return;
}
private createCustomUserSet(filter: string): [UserSetType, 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 userSetType = UserSetType.IncludeUser;
if (keyword === 'include') {
userSetType = UserSetType.IncludeUser;
} else if (keyword === 'exclude') {
userSetType = UserSetType.ExcludeUser;
} else if (keyword === 'includegroup') {
userSetType = UserSetType.IncludeGroup;
} else if (keyword === 'excludegroup') {
userSetType = UserSetType.ExcludeGroup;
} else {
return null;
}
const set = new Set<string>();
const pieces = parts[1].split(',');
for (const p of pieces) {
set.add(p.trim().toLowerCase());
}
return [userSetType, set];
let users: UserEntry[];
if (this.syncConfig.users) {
users = await this.getCurrentUsers();
const deletedUsers = await this.getDeletedUsers(force, !test);
users = users.concat(deletedUsers);
}
private async filterOutUserResult(setFilter: [UserSetType, Set<string>], user: UserEntry): Promise<boolean> {
if (setFilter == null) {
return false;
}
let userSetTypeExclude = null;
if (setFilter[0] === UserSetType.IncludeUser) {
userSetTypeExclude = false;
} else if (setFilter[0] === UserSetType.ExcludeUser) {
userSetTypeExclude = true;
}
if (userSetTypeExclude != null) {
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 { }
return false;
let groups: GroupEntry[];
if (this.syncConfig.groups) {
const setFilter = await this.createAadCustomSet(this.syncConfig.groupFilter);
groups = await this.getGroups(setFilter);
users = this.filterUsersFromGroupsSet(users, groups, setFilter, this.syncConfig);
}
private buildUser(user: graphType.User): UserEntry {
const entry = new UserEntry();
entry.referenceId = user.id;
entry.externalId = user.id;
entry.email = user.mail;
return [groups, users];
}
if (user.userPrincipalName && (entry.email == null || entry.email === '' ||
entry.email.indexOf('onmicrosoft.com') > -1)) {
entry.email = user.userPrincipalName;
}
private async getCurrentUsers(): Promise<UserEntry[]> {
const entryIds = new Set<string>();
let entries: UserEntry[] = [];
let users: graphType.User[];
const setFilter = this.createCustomUserSet(this.syncConfig.userFilter);
const userIdsToExclude = new Set<string>();
if (entry.email != null) {
entry.email = entry.email.trim().toLowerCase();
}
// Only get users for the groups provided in includeGroup filter
if (setFilter != null && setFilter[0] === UserSetType.IncludeGroup) {
users = await this.getUsersByGroups(setFilter);
// Get the users in the excludedGroups and filter them out from all users
} else if (setFilter != null && setFilter[0] === UserSetType.ExcludeGroup) {
(await this.getUsersByGroups(setFilter)).forEach((user: graphType.User) =>
userIdsToExclude.add(user.id)
);
const userReq = this.client.api("/users" + UserSelectParams);
users = await this.getUsersByResource(userReq);
} else {
const userReq = this.client.api("/users" + UserSelectParams);
users = await this.getUsersByResource(userReq);
}
if (users != null) {
entries = await this.buildUserEntries(users, userIdsToExclude, setFilter);
}
return entries;
}
entry.disabled = user.accountEnabled == null ? false : !user.accountEnabled;
private async getDeletedUsers(force: boolean, saveDelta: boolean): Promise<UserEntry[]> {
const entryIds = new Set<string>();
const entries: UserEntry[] = [];
if ((user as any)['@removed'] != null && (user as any)['@removed'].reason === 'changed') {
entry.deleted = true;
}
return entry;
let res: any = null;
const token = await this.stateService.getUserDelta();
if (!force && token != null) {
try {
const deltaReq = this.client.api(token);
res = await deltaReq.get();
} catch {
res = null;
}
}
private async getGroups(setFilter: [boolean, Set<string>]): Promise<GroupEntry[]> {
const entryIds = new Set<string>();
const entries: GroupEntry[] = [];
const groupsReq = this.client.api('/groups');
let res = await groupsReq.get();
while (true) {
const groups: graphType.Group[] = res.value;
if (groups != null) {
for (const group of groups) {
if (group.id == null || entryIds.has(group.id)) {
continue;
}
if (this.filterOutResult(setFilter, group.displayName)) {
continue;
}
const entry = await this.buildGroup(group);
entries.push(entry);
entryIds.add(group.id);
}
}
if (res[NextLink] == null) {
break;
} else {
const nextReq = this.client.api(res[NextLink]);
res = await nextReq.get();
}
}
return entries;
if (res == null) {
const userReq = this.client.api("/users/delta" + UserSelectParams);
res = await userReq.get();
}
private async buildGroup(group: graphType.Group): Promise<GroupEntry> {
const entry = new GroupEntry();
entry.referenceId = group.id;
entry.externalId = group.id;
entry.name = group.displayName;
const setFilter = this.createCustomUserSet(this.syncConfig.userFilter);
while (true) {
const users: graphType.User[] = res.value;
if (users != null) {
for (const user of users) {
if (user.id == null || entryIds.has(user.id)) {
continue;
}
const entry = this.buildUser(user);
if (!entry.deleted) {
continue;
}
const memReq = this.client.api('/groups/' + group.id + '/members');
const memRes = await memReq.get();
const members: any = memRes.value;
if (members != null) {
for (const member of members) {
if (member[ObjectType] === '#microsoft.graph.group') {
entry.groupMemberReferenceIds.add((member as graphType.Group).id);
} else if (member[ObjectType] === '#microsoft.graph.user') {
entry.userMemberExternalIds.add((member as graphType.User).id);
}
}
if (
setFilter != null &&
(setFilter[0] === UserSetType.IncludeUser ||
setFilter[0] === UserSetType.ExcludeUser) &&
(await this.filterOutUserResult(setFilter, entry))
) {
continue;
}
entries.push(entry);
entryIds.add(user.id);
}
}
return entry;
if (res[NextLink] == null) {
if (res[DeltaLink] != null && saveDelta) {
await this.stateService.setUserDelta(res[DeltaLink]);
}
break;
} else {
const nextReq = this.client.api(res[NextLink]);
res = await nextReq.get();
}
}
private init() {
this.client = graph.Client.init({
authProvider: (done) => {
if (this.dirConfig.applicationId == null || this.dirConfig.key == null ||
this.dirConfig.tenant == null) {
done(this.i18nService.t('dirConfigIncomplete'), null);
return;
}
return entries;
}
if (!this.accessTokenIsExpired()) {
done(null, this.accessToken);
return;
}
private async createAadCustomSet(filter: string): Promise<[boolean, Set<string>]> {
if (filter == null || filter === "") {
return null;
}
this.accessToken = null;
this.accessTokenExpiration = null;
const mainParts = filter.split("|");
if (mainParts.length < 1 || mainParts[0] == null || mainParts[0].trim() === "") {
return null;
}
const data = querystring.stringify({
client_id: this.dirConfig.applicationId,
client_secret: this.dirConfig.key,
grant_type: 'client_credentials',
scope: 'https://graph.microsoft.com/.default',
});
const parts = mainParts[0].split(":");
if (parts.length !== 2) {
return null;
}
const req = https.request({
host: 'login.microsoftonline.com',
path: '/' + this.dirConfig.tenant + '/oauth2/v2.0/token',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(data),
},
}, (res) => {
res.setEncoding('utf8');
res.on('data', (chunk: string) => {
const d = JSON.parse(chunk);
if (res.statusCode === 200 && d.access_token != null) {
this.setAccessTokenExpiration(d.access_token, d.expires_in);
done(null, d.access_token);
} else if (d.error != null && d.error_description != null) {
done(d.error + ' (' + res.statusCode + '): ' + d.error_description, null);
} else {
done('Unknown error (' + res.statusCode + ').', null);
}
});
}).on('error', (err) => {
done(err, 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;
}
req.write(data);
req.end();
},
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;
}
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 userSetType = UserSetType.IncludeUser;
if (keyword === "include") {
userSetType = UserSetType.IncludeUser;
} else if (keyword === "exclude") {
userSetType = UserSetType.ExcludeUser;
} else if (keyword === "includegroup") {
userSetType = UserSetType.IncludeGroup;
} else if (keyword === "excludegroup") {
userSetType = UserSetType.ExcludeGroup;
} else {
return null;
}
const set = new Set<string>();
const pieces = parts[1].split(",");
for (const p of pieces) {
set.add(p.trim().toLowerCase());
}
return [userSetType, set];
}
private async filterOutUserResult(
setFilter: [UserSetType, Set<string>],
user: UserEntry
): Promise<boolean> {
if (setFilter == null) {
return false;
}
let userSetTypeExclude = null;
if (setFilter[0] === UserSetType.IncludeUser) {
userSetTypeExclude = false;
} else if (setFilter[0] === UserSetType.ExcludeUser) {
userSetTypeExclude = true;
}
if (userSetTypeExclude != null) {
return this.filterOutResult([userSetTypeExclude, setFilter[1]], user.email);
}
return false;
}
private buildUser(user: graphType.User): UserEntry {
const entry = new UserEntry();
entry.referenceId = user.id;
entry.externalId = user.id;
entry.email = user.mail;
if (
user.userPrincipalName &&
(entry.email == null || entry.email === "" || entry.email.indexOf("onmicrosoft.com") > -1)
) {
entry.email = user.userPrincipalName;
}
if (entry.email != null) {
entry.email = entry.email.trim().toLowerCase();
}
entry.disabled = user.accountEnabled == null ? false : !user.accountEnabled;
if ((user as any)["@removed"] != null && (user as any)["@removed"].reason === "changed") {
entry.deleted = true;
}
return entry;
}
private async getGroups(setFilter: [boolean, Set<string>]): Promise<GroupEntry[]> {
const entryIds = new Set<string>();
const entries: GroupEntry[] = [];
const groupsReq = this.client.api("/groups");
let res = await groupsReq.get();
while (true) {
const groups: graphType.Group[] = res.value;
if (groups != null) {
for (const group of groups) {
if (group.id == null || entryIds.has(group.id)) {
continue;
}
if (this.filterOutResult(setFilter, group.displayName)) {
continue;
}
const entry = await this.buildGroup(group);
entries.push(entry);
entryIds.add(group.id);
}
}
if (res[NextLink] == null) {
break;
} else {
const nextReq = this.client.api(res[NextLink]);
res = await nextReq.get();
}
}
return entries;
}
private async getUsersByResource(usersRequest: graph.GraphRequest) {
const users: graphType.User[] = [];
let res = await usersRequest.get();
res.value.forEach((user: graphType.User) => users.push(user));
while (res[NextLink] != null) {
const nextReq = this.client.api(res[NextLink]);
res = await nextReq.get();
res.value.forEach((user: graphType.User) => users.push(user));
}
return users;
}
private async getUsersByGroups(setFilter: [UserSetType, Set<string>]): Promise<graphType.User[]> {
const users: graphType.User[] = [];
for (const group of setFilter[1]) {
const groupUsersReq = this.client.api(
`/groups/${group}/transitiveMembers` + UserSelectParams
);
users.push(...(await this.getUsersByResource(groupUsersReq)));
}
return users;
}
private async buildUserEntries(
users: graphType.User[],
userIdsToExclude: Set<string>,
setFilter: [UserSetType, Set<string>]
) {
const entryIds = new Set<string>();
const entries: UserEntry[] = [];
for (const user of users) {
if (user.id == null || entryIds.has(user.id) || userIdsToExclude.has(user.id)) {
continue;
}
const entry = this.buildUser(user);
if (
setFilter != null &&
(setFilter[0] === UserSetType.IncludeUser || setFilter[0] === UserSetType.ExcludeUser) &&
(await this.filterOutUserResult(setFilter, entry))
) {
continue;
}
if (!this.isInvalidUser(entry)) {
entries.push(entry);
entryIds.add(user.id);
}
}
return entries;
}
private isInvalidUser(user: UserEntry): boolean {
return !user.disabled && !user.deleted && (user.email == null || user.email.indexOf("#") > -1);
}
private async buildGroup(group: graphType.Group): Promise<GroupEntry> {
const entry = new GroupEntry();
entry.referenceId = group.id;
entry.externalId = group.id;
entry.name = group.displayName;
const memReq = this.client.api("/groups/" + group.id + "/members");
let memRes = await memReq.get();
while (true) {
const members: any = memRes.value;
if (members != null) {
for (const member of members) {
if (member[ObjectType] === "#microsoft.graph.group") {
entry.groupMemberReferenceIds.add((member as graphType.Group).id);
} else if (member[ObjectType] === "#microsoft.graph.user") {
entry.userMemberExternalIds.add((member as graphType.User).id);
}
}
}
if (memRes[NextLink] == null) {
break;
} else {
const nextMemReq = this.client.api(memRes[NextLink]);
memRes = await nextMemReq.get();
}
}
return entry;
}
private init() {
this.client = graph.Client.init({
authProvider: (done) => {
if (
this.dirConfig.applicationId == null ||
this.dirConfig.key == null ||
this.dirConfig.tenant == null
) {
done(new Error(this.i18nService.t("dirConfigIncomplete")), null);
return;
}
const identityAuthority =
this.dirConfig.identityAuthority != null
? this.dirConfig.identityAuthority
: AzurePublicIdentityAuhtority;
if (
identityAuthority !== AzurePublicIdentityAuhtority &&
identityAuthority !== AzureGovermentIdentityAuhtority
) {
done(new Error(this.i18nService.t("dirConfigIncomplete")), null);
return;
}
if (!this.accessTokenIsExpired()) {
done(null, this.accessToken);
return;
}
this.accessToken = null;
this.accessTokenExpiration = null;
const data = querystring.stringify({
client_id: this.dirConfig.applicationId,
client_secret: this.dirConfig.key,
grant_type: "client_credentials",
scope: "https://graph.microsoft.com/.default",
});
const req = https
.request(
{
host: identityAuthority,
path: "/" + this.dirConfig.tenant + "/oauth2/v2.0/token",
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Content-Length": Buffer.byteLength(data),
},
},
(res) => {
res.setEncoding("utf8");
res.on("data", (chunk: string) => {
const d = JSON.parse(chunk);
if (res.statusCode === 200 && d.access_token != null) {
this.setAccessTokenExpiration(d.access_token, d.expires_in);
done(null, d.access_token);
} else if (d.error != null && d.error_description != null) {
const shortError = d.error_description?.split("\n", 1)[0];
const err = new Error(d.error + " (" + res.statusCode + "): " + shortError);
// tslint:disable-next-line
console.error(d.error_description);
done(err, null);
} else {
const err = new Error("Unknown error (" + res.statusCode + ").");
done(err, null);
}
});
}
)
.on("error", (err) => {
done(err, null);
});
req.write(data);
req.end();
},
});
}
private accessTokenIsExpired() {
if (this.accessToken == null || this.accessTokenExpiration == null) {
return true;
}
private accessTokenIsExpired() {
if (this.accessToken == null || this.accessTokenExpiration == null) {
return true;
}
// expired if less than 2 minutes til expiration
const now = new Date();
return this.accessTokenExpiration.getTime() - now.getTime() < 120000;
}
// expired if less than 2 minutes til expiration
const now = new Date();
return this.accessTokenExpiration.getTime() - now.getTime() < 120000;
private setAccessTokenExpiration(accessToken: string, expSeconds: number) {
if (accessToken == null || expSeconds == null) {
return;
}
private setAccessTokenExpiration(accessToken: string, expSeconds: number) {
if (accessToken == null || expSeconds == null) {
return;
}
this.accessToken = accessToken;
const exp = new Date();
exp.setSeconds(exp.getSeconds() + expSeconds);
this.accessTokenExpiration = exp;
}
this.accessToken = accessToken;
const exp = new Date();
exp.setSeconds(exp.getSeconds() + expSeconds);
this.accessTokenExpiration = exp;
}
}

View File

@@ -1,86 +1,95 @@
import { GroupEntry } from '../models/groupEntry';
import { UserEntry } from '../models/userEntry';
import { SyncConfiguration } from "../models/syncConfiguration";
import { GroupEntry } from "../models/groupEntry";
import { UserEntry } from "../models/userEntry";
export abstract class BaseDirectoryService {
protected createDirectoryQuery(filter: string) {
if (filter == null || filter === '') {
return null;
}
const mainParts = filter.split('|');
if (mainParts.length < 2 || mainParts[1] == null || mainParts[1].trim() === '') {
return null;
}
return mainParts[1].trim();
protected createDirectoryQuery(filter: string) {
if (filter == null || filter === "") {
return null;
}
protected createCustomSet(filter: string): [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 {
return null;
}
const set = new Set<string>();
const pieces = parts[1].split(',');
for (const p of pieces) {
set.add(p.trim().toLowerCase());
}
return [exclude, set];
const mainParts = filter.split("|");
if (mainParts.length < 2 || mainParts[1] == null || mainParts[1].trim() === "") {
return null;
}
protected filterOutResult(setFilter: [boolean, Set<string>], result: string) {
if (setFilter != null) {
const cleanResult = result != null ? result.trim().toLowerCase() : '--';
const excluded = setFilter[0];
const set = setFilter[1];
return mainParts[1].trim();
}
if (excluded && set.has(cleanResult)) {
return true;
} else if (!excluded && !set.has(cleanResult)) {
return true;
}
}
return false;
protected createCustomSet(filter: string): [boolean, Set<string>] {
if (filter == null || filter === "") {
return null;
}
protected filterUsersFromGroupsSet(users: UserEntry[], groups: GroupEntry[],
setFilter: [boolean, Set<string>]): UserEntry[] {
if (setFilter == null || users == null) {
return users;
}
return users.filter((u) => {
if (u.disabled || u.deleted) {
return true;
}
return groups.filter((g) => g.userMemberExternalIds.has(u.externalId)).length > 0;
});
const mainParts = filter.split("|");
if (mainParts.length < 1 || mainParts[0] == null || mainParts[0].trim() === "") {
return null;
}
protected forceGroup(force: boolean, users: UserEntry[]): boolean {
return force || (users != null && users.filter((u) => !u.deleted && !u.disabled).length > 0);
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 {
return null;
}
const set = new Set<string>();
const pieces = parts[1].split(",");
for (const p of pieces) {
set.add(p.trim().toLowerCase());
}
return [exclude, set];
}
protected filterOutResult(setFilter: [boolean, Set<string>], result: string) {
if (setFilter != null) {
const cleanResult = result != null ? result.trim().toLowerCase() : "--";
const excluded = setFilter[0];
const set = setFilter[1];
if (excluded && set.has(cleanResult)) {
return true;
} else if (!excluded && !set.has(cleanResult)) {
return true;
}
}
return false;
}
protected filterUsersFromGroupsSet(
users: UserEntry[],
groups: GroupEntry[],
setFilter: [boolean, Set<string>],
syncConfig: SyncConfiguration
): UserEntry[] {
if (setFilter == null || users == null) {
return users;
}
return users.filter((u) => {
if (u.deleted) {
return true;
}
if (u.disabled && syncConfig.removeDisabled) {
return true;
}
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);
}
}

View File

@@ -1,210 +0,0 @@
import { DirectoryType } from '../enums/directoryType';
import { StorageService } from 'jslib/abstractions/storage.service';
import { AzureConfiguration } from '../models/azureConfiguration';
import { GSuiteConfiguration } from '../models/gsuiteConfiguration';
import { LdapConfiguration } from '../models/ldapConfiguration';
import { OktaConfiguration } from '../models/oktaConfiguration';
import { SyncConfiguration } from '../models/syncConfiguration';
const StoredSecurely = '[STORED SECURELY]';
const Keys = {
ldap: 'ldapPassword',
gsuite: 'gsuitePrivateKey',
azure: 'azureKey',
okta: 'oktaToken',
directoryConfigPrefix: 'directoryConfig_',
sync: 'syncConfig',
directoryType: 'directoryType',
userDelta: 'userDeltaToken',
groupDelta: 'groupDeltaToken',
lastUserSync: 'lastUserSync',
lastGroupSync: 'lastGroupSync',
lastSyncHash: 'lastSyncHash',
organizationId: 'organizationId',
};
export class ConfigurationService {
constructor(private storageService: StorageService, private secureStorageService: StorageService) { }
async getDirectory<T>(type: DirectoryType): Promise<T> {
const config = await this.storageService.get<T>(Keys.directoryConfigPrefix + type);
if (config == null) {
return config;
}
switch (type) {
case DirectoryType.Ldap:
(config as any).password = await this.secureStorageService.get<string>(Keys.ldap);
break;
case DirectoryType.AzureActiveDirectory:
(config as any).key = await this.secureStorageService.get<string>(Keys.azure);
break;
case DirectoryType.Okta:
(config as any).token = await this.secureStorageService.get<string>(Keys.okta);
break;
case DirectoryType.GSuite:
(config as any).privateKey = await this.secureStorageService.get<string>(Keys.gsuite);
break;
}
return config;
}
async saveDirectory(type: DirectoryType,
config: LdapConfiguration | GSuiteConfiguration | AzureConfiguration | OktaConfiguration): Promise<any> {
const savedConfig: any = Object.assign({}, config);
switch (type) {
case DirectoryType.Ldap:
if (savedConfig.password == null) {
await this.secureStorageService.remove(Keys.ldap);
} else {
await this.secureStorageService.save(Keys.ldap, savedConfig.password);
savedConfig.password = StoredSecurely;
}
break;
case DirectoryType.AzureActiveDirectory:
if (savedConfig.key == null) {
await this.secureStorageService.remove(Keys.azure);
} else {
await this.secureStorageService.save(Keys.azure, savedConfig.key);
savedConfig.key = StoredSecurely;
}
break;
case DirectoryType.Okta:
if (savedConfig.token == null) {
await this.secureStorageService.remove(Keys.okta);
} else {
await this.secureStorageService.save(Keys.okta, savedConfig.token);
savedConfig.token = StoredSecurely;
}
break;
case DirectoryType.GSuite:
if (savedConfig.privateKey == null) {
await this.secureStorageService.remove(Keys.gsuite);
} else {
(config as GSuiteConfiguration).privateKey = savedConfig.privateKey =
savedConfig.privateKey.replace(/\\n/g, '\n');
await this.secureStorageService.save(Keys.gsuite, savedConfig.privateKey);
savedConfig.privateKey = StoredSecurely;
}
break;
}
await this.storageService.save(Keys.directoryConfigPrefix + type, savedConfig);
}
getSync(): Promise<SyncConfiguration> {
return this.storageService.get<SyncConfiguration>(Keys.sync);
}
saveSync(config: SyncConfiguration) {
return this.storageService.save(Keys.sync, config);
}
getDirectoryType(): Promise<DirectoryType> {
return this.storageService.get<DirectoryType>(Keys.directoryType);
}
async saveDirectoryType(type: DirectoryType) {
const currentType = await this.getDirectoryType();
if (type !== currentType) {
await this.clearStatefulSettings();
}
return this.storageService.save(Keys.directoryType, type);
}
getUserDeltaToken(): Promise<string> {
return this.storageService.get<string>(Keys.userDelta);
}
saveUserDeltaToken(token: string) {
if (token == null) {
return this.storageService.remove(Keys.userDelta);
} else {
return this.storageService.save(Keys.userDelta, token);
}
}
getGroupDeltaToken(): Promise<string> {
return this.storageService.get<string>(Keys.groupDelta);
}
saveGroupDeltaToken(token: string) {
if (token == null) {
return this.storageService.remove(Keys.groupDelta);
} else {
return this.storageService.save(Keys.groupDelta, token);
}
}
async getLastUserSyncDate(): Promise<Date> {
const dateString = await this.storageService.get<string>(Keys.lastUserSync);
if (dateString == null) {
return null;
}
return new Date(dateString);
}
saveLastUserSyncDate(date: Date) {
if (date == null) {
return this.storageService.remove(Keys.lastUserSync);
} else {
return this.storageService.save(Keys.lastUserSync, date);
}
}
async getLastGroupSyncDate(): Promise<Date> {
const dateString = await this.storageService.get<string>(Keys.lastGroupSync);
if (dateString == null) {
return null;
}
return new Date(dateString);
}
saveLastGroupSyncDate(date: Date) {
if (date == null) {
return this.storageService.remove(Keys.lastGroupSync);
} else {
return this.storageService.save(Keys.lastGroupSync, date);
}
}
getLastSyncHash(): Promise<string> {
return this.storageService.get<string>(Keys.lastSyncHash);
}
saveLastSyncHash(hash: string) {
if (hash == null) {
return this.storageService.remove(Keys.lastSyncHash);
} else {
return this.storageService.save(Keys.lastSyncHash, hash);
}
}
getOrganizationId(): Promise<string> {
return this.storageService.get<string>(Keys.organizationId);
}
async saveOrganizationId(id: string) {
const currentId = await this.getOrganizationId();
if (currentId !== id) {
await this.clearStatefulSettings();
}
if (id == null) {
return this.storageService.remove(Keys.organizationId);
} else {
return this.storageService.save(Keys.organizationId, id);
}
}
async clearStatefulSettings(hashToo = false) {
await this.saveUserDeltaToken(null);
await this.saveGroupDeltaToken(null);
await this.saveLastGroupSyncDate(null);
await this.saveLastUserSyncDate(null);
if (hashToo) {
await this.saveLastSyncHash(null);
}
}
}

View File

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

View File

@@ -1,213 +1,260 @@
import { JWT } from 'google-auth-library';
import {
admin_directory_v1,
google,
} from 'googleapis';
import { JWT } from "google-auth-library";
import { admin_directory_v1, google } from "googleapis";
import { DirectoryType } from '../enums/directoryType';
import { DirectoryType } from "../enums/directoryType";
import { GroupEntry } from '../models/groupEntry';
import { GSuiteConfiguration } from '../models/gsuiteConfiguration';
import { SyncConfiguration } from '../models/syncConfiguration';
import { UserEntry } from '../models/userEntry';
import { GroupEntry } from "../models/groupEntry";
import { GSuiteConfiguration } from "../models/gsuiteConfiguration";
import { SyncConfiguration } from "../models/syncConfiguration";
import { UserEntry } from "../models/userEntry";
import { BaseDirectoryService } from './baseDirectory.service';
import { ConfigurationService } from './configuration.service';
import { DirectoryService } from './directory.service';
import { BaseDirectoryService } from "./baseDirectory.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 { StateService } from "../abstractions/state.service";
export class GSuiteDirectoryService extends BaseDirectoryService implements DirectoryService {
private client: JWT;
private service: admin_directory_v1.Admin;
private authParams: any;
private dirConfig: GSuiteConfiguration;
private syncConfig: SyncConfiguration;
export class GSuiteDirectoryService extends BaseDirectoryService implements IDirectoryService {
private client: JWT;
private service: admin_directory_v1.Admin;
private authParams: any;
private dirConfig: GSuiteConfiguration;
private syncConfig: SyncConfiguration;
constructor(private configurationService: ConfigurationService, private logService: LogService,
private i18nService: I18nService) {
super();
this.service = google.admin('directory_v1');
constructor(
private logService: LogService,
private i18nService: I18nService,
private stateService: StateService
) {
super();
this.service = google.admin("directory_v1");
}
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
const type = await this.stateService.getDirectoryType();
if (type !== DirectoryType.GSuite) {
return;
}
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
const type = await this.configurationService.getDirectoryType();
if (type !== DirectoryType.GSuite) {
return;
}
this.dirConfig = await this.configurationService.getDirectory<GSuiteConfiguration>(DirectoryType.GSuite);
if (this.dirConfig == null) {
return;
}
this.syncConfig = await this.configurationService.getSync();
if (this.syncConfig == null) {
return;
}
await this.auth();
let users: UserEntry[];
if (this.syncConfig.users) {
users = await this.getUsers();
}
let groups: GroupEntry[];
if (this.syncConfig.groups) {
const setFilter = this.createCustomSet(this.syncConfig.groupFilter);
groups = await this.getGroups(setFilter);
users = this.filterUsersFromGroupsSet(users, groups, setFilter);
}
return [groups, users];
this.dirConfig = await this.stateService.getDirectory<GSuiteConfiguration>(
DirectoryType.GSuite
);
if (this.dirConfig == null) {
return;
}
private async getUsers(): Promise<UserEntry[]> {
const entries: UserEntry[] = [];
const query = this.createDirectoryQuery(this.syncConfig.userFilter);
this.logService.info('Querying users.');
let p = Object.assign({ query: query }, this.authParams);
const res = await this.service.users.list(p);
if (res.status !== 200) {
throw new Error('User list API failed: ' + res.statusText);
}
const filter = this.createCustomSet(this.syncConfig.userFilter);
if (res.data.users != null) {
for (const user of res.data.users) {
if (this.filterOutResult(filter, user.primaryEmail)) {
continue;
}
const entry = this.buildUser(user, false);
if (entry != null) {
entries.push(entry);
}
}
}
this.logService.info('Querying deleted users.');
p = Object.assign({ showDeleted: true, query: query }, this.authParams);
const delRes = await this.service.users.list(p);
if (delRes.status !== 200) {
throw new Error('Deleted user list API failed: ' + delRes.statusText);
}
if (delRes.data.users != null) {
for (const user of delRes.data.users) {
if (this.filterOutResult(filter, user.primaryEmail)) {
continue;
}
const entry = this.buildUser(user, true);
if (entry != null) {
entries.push(entry);
}
}
}
return entries;
this.syncConfig = await this.stateService.getSync();
if (this.syncConfig == null) {
return;
}
private buildUser(user: admin_directory_v1.Schema$User, deleted: boolean) {
if ((user.emails == null || user.emails === '') && !deleted) {
return null;
}
await this.auth();
const entry = new UserEntry();
entry.referenceId = user.id;
entry.externalId = user.id;
entry.email = user.primaryEmail != null ? user.primaryEmail.trim().toLowerCase() : null;
entry.disabled = user.suspended || false;
entry.deleted = deleted;
let users: UserEntry[] = [];
if (this.syncConfig.users) {
users = await this.getUsers();
}
let groups: GroupEntry[];
if (this.syncConfig.groups) {
const setFilter = this.createCustomSet(this.syncConfig.groupFilter);
groups = await this.getGroups(setFilter, users);
users = this.filterUsersFromGroupsSet(users, groups, setFilter, this.syncConfig);
}
return [groups, users];
}
private async getUsers(): Promise<UserEntry[]> {
const entries: UserEntry[] = [];
const query = this.createDirectoryQuery(this.syncConfig.userFilter);
let nextPageToken: string = null;
const filter = this.createCustomSet(this.syncConfig.userFilter);
while (true) {
this.logService.info("Querying users - nextPageToken:" + nextPageToken);
const p = Object.assign({ query: query, pageToken: nextPageToken }, this.authParams);
const res = await this.service.users.list(p);
if (res.status !== 200) {
throw new Error("User list API failed: " + res.statusText);
}
nextPageToken = res.data.nextPageToken;
if (res.data.users != null) {
for (const user of res.data.users) {
if (this.filterOutResult(filter, user.primaryEmail)) {
continue;
}
const entry = this.buildUser(user, false);
if (entry != null) {
entries.push(entry);
}
}
}
if (nextPageToken == null) {
break;
}
}
nextPageToken = null;
while (true) {
this.logService.info("Querying deleted users - nextPageToken:" + nextPageToken);
const p = Object.assign(
{ showDeleted: true, query: query, pageToken: nextPageToken },
this.authParams
);
const delRes = await this.service.users.list(p);
if (delRes.status !== 200) {
throw new Error("Deleted user list API failed: " + delRes.statusText);
}
nextPageToken = delRes.data.nextPageToken;
if (delRes.data.users != null) {
for (const user of delRes.data.users) {
if (this.filterOutResult(filter, user.primaryEmail)) {
continue;
}
const entry = this.buildUser(user, true);
if (entry != null) {
entries.push(entry);
}
}
}
if (nextPageToken == null) {
break;
}
}
return entries;
}
private buildUser(user: admin_directory_v1.Schema$User, deleted: boolean) {
if ((user.emails == null || user.emails === "") && !deleted) {
return null;
}
const entry = new UserEntry();
entry.referenceId = user.id;
entry.externalId = user.id;
entry.email = user.primaryEmail != null ? user.primaryEmail.trim().toLowerCase() : null;
entry.disabled = user.suspended || false;
entry.deleted = deleted;
return entry;
}
private async getGroups(
setFilter: [boolean, Set<string>],
users: UserEntry[]
): Promise<GroupEntry[]> {
const entries: GroupEntry[] = [];
let nextPageToken: string = null;
while (true) {
this.logService.info("Querying groups - nextPageToken:" + nextPageToken);
const p = Object.assign({ pageToken: nextPageToken }, this.authParams);
const res = await this.service.groups.list(p);
if (res.status !== 200) {
throw new Error("Group list API failed: " + res.statusText);
}
nextPageToken = res.data.nextPageToken;
if (res.data.groups != null) {
for (const group of res.data.groups) {
if (!this.filterOutResult(setFilter, group.name)) {
const entry = await this.buildGroup(group, users);
entries.push(entry);
}
}
}
if (nextPageToken == null) {
break;
}
}
return entries;
}
private async buildGroup(group: admin_directory_v1.Schema$Group, users: UserEntry[]) {
let nextPageToken: string = null;
const entry = new GroupEntry();
entry.referenceId = group.id;
entry.externalId = group.id;
entry.name = group.name;
while (true) {
const p = Object.assign({ groupKey: group.id, pageToken: nextPageToken }, this.authParams);
const memRes = await this.service.members.list(p);
if (memRes.status !== 200) {
this.logService.warning("Group member list API failed: " + memRes.statusText);
return entry;
}
}
private async getGroups(setFilter: [boolean, Set<string>]): Promise<GroupEntry[]> {
const entries: GroupEntry[] = [];
this.logService.info('Querying groups.');
const res = await this.service.groups.list(this.authParams);
if (res.status !== 200) {
throw new Error('Group list API failed: ' + res.statusText);
}
if (res.data.groups != null) {
for (const group of res.data.groups) {
if (!this.filterOutResult(setFilter, group.name)) {
const entry = await this.buildGroup(group);
entries.push(entry);
}
nextPageToken = memRes.data.nextPageToken;
if (memRes.data.members != null) {
for (const member of memRes.data.members) {
if (member.type == null) {
continue;
}
const type = member.type.toLowerCase();
if (type === "user") {
if (member.status == null || member.status.toLowerCase() !== "active") {
continue;
}
}
return entries;
}
private async buildGroup(group: admin_directory_v1.Schema$Group) {
const entry = new GroupEntry();
entry.referenceId = group.id;
entry.externalId = group.id;
entry.name = group.name;
const p = Object.assign({ groupKey: group.id }, this.authParams);
const memRes = await this.service.members.list(p);
if (memRes.status !== 200) {
this.logService.warning('Group member list API failed: ' + memRes.statusText);
return entry;
}
if (memRes.data.members != null) {
for (const member of memRes.data.members) {
if (member.type == null) {
continue;
}
if (member.role == null || member.role.toLowerCase() !== 'member') {
continue;
}
if (member.status == null || member.status.toLowerCase() !== 'active') {
continue;
}
const type = member.type.toLowerCase();
if (type === 'user') {
entry.userMemberExternalIds.add(member.id);
} else if (type === 'group') {
entry.groupMemberReferenceIds.add(member.id);
}
entry.userMemberExternalIds.add(member.id);
} else if (type === "group") {
entry.groupMemberReferenceIds.add(member.id);
} else if (type === "customer") {
for (const user of users) {
entry.userMemberExternalIds.add(user.externalId);
}
}
}
}
return entry;
if (nextPageToken == null) {
break;
}
}
private async auth() {
if (this.dirConfig.clientEmail == null || this.dirConfig.privateKey == null ||
this.dirConfig.adminUser == null || this.dirConfig.domain == null) {
throw new Error(this.i18nService.t('dirConfigIncomplete'));
}
return entry;
}
this.client = new google.auth.JWT({
email: this.dirConfig.clientEmail,
key: this.dirConfig.privateKey,
subject: this.dirConfig.adminUser,
scopes: [
'https://www.googleapis.com/auth/admin.directory.user.readonly',
'https://www.googleapis.com/auth/admin.directory.group.readonly',
'https://www.googleapis.com/auth/admin.directory.group.member.readonly',
],
});
await this.client.authorize();
this.authParams = {
auth: this.client,
domain: this.dirConfig.domain,
};
if (this.dirConfig.customer != null) {
this.authParams.customer = this.dirConfig.customer;
}
private async auth() {
if (
this.dirConfig.clientEmail == null ||
this.dirConfig.privateKey == null ||
this.dirConfig.adminUser == null ||
this.dirConfig.domain == null
) {
throw new Error(this.i18nService.t("dirConfigIncomplete"));
}
this.client = new google.auth.JWT({
email: this.dirConfig.clientEmail,
key: this.dirConfig.privateKey != null ? this.dirConfig.privateKey.trimLeft() : null,
subject: this.dirConfig.adminUser,
scopes: [
"https://www.googleapis.com/auth/admin.directory.user.readonly",
"https://www.googleapis.com/auth/admin.directory.group.readonly",
"https://www.googleapis.com/auth/admin.directory.group.member.readonly",
],
});
await this.client.authorize();
this.authParams = {
auth: this.client,
};
if (this.dirConfig.domain != null && this.dirConfig.domain.trim() !== "") {
this.authParams.domain = this.dirConfig.domain;
}
if (this.dirConfig.customer != null && this.dirConfig.customer.trim() !== "") {
this.authParams.customer = this.dirConfig.customer;
}
}
}

View File

@@ -1,15 +1,18 @@
import * as fs from 'fs';
import * as path from 'path';
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) {
super(systemLanguage, localesDirectory, (formattedLocale: string) => {
const filePath = path.join(__dirname, this.localesDirectory + '/' + formattedLocale + '/messages.json');
const localesJson = fs.readFileSync(filePath, 'utf8');
const locales = JSON.parse(localesJson.replace(/^\uFEFF/, '')); // strip the BOM
return Promise.resolve(locales);
});
}
constructor(systemLanguage: string, localesDirectory: string) {
super(systemLanguage, localesDirectory, (formattedLocale: string) => {
const filePath = path.join(
__dirname,
this.localesDirectory + "/" + formattedLocale + "/messages.json"
);
const localesJson = fs.readFileSync(filePath, "utf8");
const locales = JSON.parse(localesJson.replace(/^\uFEFF/, "")); // strip the BOM
return Promise.resolve(locales);
});
}
}

View File

@@ -1,25 +1,25 @@
import {
deletePassword,
getPassword,
setPassword,
} from 'keytar';
import { deletePassword, getPassword, 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) { }
constructor(private serviceName: string) {}
get<T>(key: string): Promise<T> {
return getPassword(this.serviceName, key).then((val) => {
return JSON.parse(val) as T;
});
}
get<T>(key: string): Promise<T> {
return getPassword(this.serviceName, key).then((val) => {
return JSON.parse(val) as T;
});
}
save(key: string, obj: any): Promise<any> {
return setPassword(this.serviceName, key, JSON.stringify(obj));
}
async has(key: string): Promise<boolean> {
return (await this.get(key)) != null;
}
remove(key: string): Promise<any> {
return deletePassword(this.serviceName, key);
}
save(key: string, obj: any): Promise<any> {
return setPassword(this.serviceName, key, JSON.stringify(obj));
}
remove(key: string): Promise<any> {
return deletePassword(this.serviceName, key);
}
}

Some files were not shown because too many files have changed in this diff Show More