mirror of
https://github.com/bitwarden/directory-connector
synced 2026-02-26 17:23:15 +00:00
Compare commits
3 Commits
jslib-remo
...
restructur
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
651867a2e9 | ||
|
|
2f713578a6 | ||
|
|
3af1e31168 |
25
.github/workflows/integration-test.yml
vendored
25
.github/workflows/integration-test.yml
vendored
@@ -14,9 +14,9 @@ on:
|
||||
- "docker-compose.yml" # any change to Docker configuration
|
||||
- "package.json" # dependencies
|
||||
- "utils/**" # any change to test fixtures
|
||||
- "src/services/sync.service.ts" # core sync service used by all directory services
|
||||
- "src/services/directory-services/ldap-directory.service*" # LDAP directory service
|
||||
- "src/services/directory-services/gsuite-directory.service*" # Google Workspace directory service
|
||||
- "libs/services/sync.service.ts" # core sync service used by all directory services
|
||||
- "libs/services/directory-services/ldap-directory.service*" # LDAP directory service
|
||||
- "libs/services/directory-services/gsuite-directory.service*" # Google Workspace directory service
|
||||
# Add directory services here as we add test coverage
|
||||
pull_request:
|
||||
paths:
|
||||
@@ -24,9 +24,9 @@ on:
|
||||
- "docker-compose.yml" # any change to Docker configuration
|
||||
- "package.json" # dependencies
|
||||
- "utils/**" # any change to test fixtures
|
||||
- "src/services/sync.service.ts" # core sync service used by all directory services
|
||||
- "src/services/directory-services/ldap-directory.service*" # LDAP directory service
|
||||
- "src/services/directory-services/gsuite-directory.service*" # Google Workspace directory service
|
||||
- "libs/services/sync.service.ts" # core sync service used by all directory services
|
||||
- "libs/services/directory-services/ldap-directory.service*" # LDAP directory service
|
||||
- "libs/services/directory-services/gsuite-directory.service*" # Google Workspace directory service
|
||||
# Add directory services here as we add test coverage
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -94,12 +94,12 @@ jobs:
|
||||
- '.github/workflows/integration-test.yml'
|
||||
- 'utils/**'
|
||||
- 'package.json'
|
||||
- 'src/services/sync.service.ts'
|
||||
- 'libs/services/sync.service.ts'
|
||||
ldap:
|
||||
- 'docker-compose.yml'
|
||||
- 'src/services/directory-services/ldap-directory.service*'
|
||||
- 'libs/services/directory-services/ldap-directory.service*'
|
||||
google:
|
||||
- 'src/services/directory-services/gsuite-directory.service*'
|
||||
- 'libs/services/directory-services/gsuite-directory.service*'
|
||||
|
||||
# LDAP
|
||||
- name: Setup LDAP integration tests
|
||||
@@ -109,6 +109,13 @@ jobs:
|
||||
sudo apt-get -y install mkcert
|
||||
npm run test:integration:setup
|
||||
|
||||
- name: Wait for LDAP container to be healthy
|
||||
if: steps.changed-files.outputs.common == 'true' || steps.changed-files.outputs.ldap == 'true'
|
||||
run: |
|
||||
echo "Waiting for LDAP container to be healthy..."
|
||||
timeout 60 bash -c 'until docker compose ps | grep open-ldap | grep -q "(healthy)"; do sleep 2; done'
|
||||
echo "LDAP container is ready!"
|
||||
|
||||
- name: Run LDAP integration tests
|
||||
if: steps.changed-files.outputs.common == 'true' || steps.changed-files.outputs.ldap == 'true'
|
||||
env:
|
||||
|
||||
13
angular.json
13
angular.json
@@ -14,7 +14,7 @@
|
||||
}
|
||||
},
|
||||
"root": ".",
|
||||
"sourceRoot": "src",
|
||||
"sourceRoot": "src-gui",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
@@ -23,12 +23,15 @@
|
||||
"outputPath": {
|
||||
"base": "dist"
|
||||
},
|
||||
"index": "src/index.html",
|
||||
"index": "src-gui/index.html",
|
||||
"tsConfig": "tsconfig.json",
|
||||
"assets": [],
|
||||
"styles": [],
|
||||
"assets": [
|
||||
{ "glob": "**/*", "input": "src-gui/images", "output": "images" },
|
||||
{ "glob": "**/*", "input": "src-gui/locales", "output": "locales" }
|
||||
],
|
||||
"styles": ["src-gui/scss/styles.scss"],
|
||||
"scripts": [],
|
||||
"browser": "src/main.ts"
|
||||
"browser": "src-gui/app/main.ts"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,3 +16,22 @@ services:
|
||||
ports:
|
||||
- "1389:1389"
|
||||
- "1636:1636"
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"ldapsearch",
|
||||
"-x",
|
||||
"-H",
|
||||
"ldap://localhost:1389",
|
||||
"-b",
|
||||
"dc=bitwarden,dc=com",
|
||||
"-D",
|
||||
"cn=admin,dc=bitwarden,dc=com",
|
||||
"-w",
|
||||
"admin",
|
||||
]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
start_period: 10s
|
||||
|
||||
@@ -87,14 +87,24 @@ export default [
|
||||
"newlines-between": "always",
|
||||
pathGroups: [
|
||||
{
|
||||
pattern: "@/jslib/**/*",
|
||||
pattern: "@/libs/**",
|
||||
group: "external",
|
||||
position: "after",
|
||||
},
|
||||
{
|
||||
pattern: "@/src/**/*",
|
||||
group: "parent",
|
||||
position: "before",
|
||||
pattern: "@/jslib/**",
|
||||
group: "external",
|
||||
position: "after",
|
||||
},
|
||||
{
|
||||
pattern: "@/src-gui/**",
|
||||
group: "external",
|
||||
position: "after",
|
||||
},
|
||||
{
|
||||
pattern: "@/src-cli/**",
|
||||
group: "external",
|
||||
position: "after",
|
||||
},
|
||||
],
|
||||
pathGroupsExcludedImportTypes: ["builtin"],
|
||||
|
||||
0
src/global.d.ts → global.d.ts
vendored
0
src/global.d.ts → global.d.ts
vendored
@@ -1,2 +1,2 @@
|
||||
// Stub file - re-exports DC EnvironmentService
|
||||
export { EnvironmentService, EnvironmentUrls } from "@/src/abstractions/environment.service";
|
||||
export { EnvironmentService, EnvironmentUrls } from "@/libs/abstractions/environment.service";
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
// Stub file - re-exports DC StateService
|
||||
export { StateService } from "@/src/abstractions/state.service";
|
||||
export { StateService } from "@/libs/abstractions/state.service";
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
// Stub file - re-exports DC TokenService
|
||||
export { TokenService } from "@/src/abstractions/token.service";
|
||||
export { TokenService } from "@/libs/abstractions/token.service";
|
||||
|
||||
@@ -10,9 +10,9 @@ import {
|
||||
Tray,
|
||||
} from "electron";
|
||||
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
import { StateService } from "@/libs/abstractions/state.service";
|
||||
|
||||
import { StateService } from "@/src/abstractions/state.service";
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
|
||||
import { WindowMain } from "./window.main";
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ import * as url from "url";
|
||||
|
||||
import { app, BrowserWindow, Rectangle, screen } from "electron";
|
||||
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
import { StateService } from "@/libs/abstractions/state.service";
|
||||
|
||||
import { StateService } from "@/src/abstractions/state.service";
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
|
||||
import { cleanUserAgent, isDev, isMacAppStore, isSnapStore } from "./utils";
|
||||
|
||||
|
||||
6
libs/abstractions/directory-factory.service.ts
Normal file
6
libs/abstractions/directory-factory.service.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { DirectoryType } from "@/libs/enums/directoryType";
|
||||
import { IDirectoryService } from "@/libs/services/directory-services/directory.service";
|
||||
|
||||
export abstract class DirectoryFactoryService {
|
||||
abstract createService(type: DirectoryType): IDirectoryService;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { OrganizationImportRequest } from "@/jslib/common/src/models/request/organizationImportRequest";
|
||||
import { GroupEntry } from "@/libs/models/groupEntry";
|
||||
import { UserEntry } from "@/libs/models/userEntry";
|
||||
|
||||
import { GroupEntry } from "@/src/models/groupEntry";
|
||||
import { UserEntry } from "@/src/models/userEntry";
|
||||
import { OrganizationImportRequest } from "@/jslib/common/src/models/request/organizationImportRequest";
|
||||
|
||||
export interface RequestBuilderOptions {
|
||||
removeDisabled: boolean;
|
||||
@@ -1,14 +1,14 @@
|
||||
import { DirectoryType } from "@/libs/enums/directoryType";
|
||||
import { EntraIdConfiguration } from "@/libs/models/entraIdConfiguration";
|
||||
import { GSuiteConfiguration } from "@/libs/models/gsuiteConfiguration";
|
||||
import { LdapConfiguration } from "@/libs/models/ldapConfiguration";
|
||||
import { OktaConfiguration } from "@/libs/models/oktaConfiguration";
|
||||
import { OneLoginConfiguration } from "@/libs/models/oneLoginConfiguration";
|
||||
import { SyncConfiguration } from "@/libs/models/syncConfiguration";
|
||||
|
||||
import { EnvironmentUrls } from "@/jslib/common/src/models/domain/environmentUrls";
|
||||
import { StorageOptions } from "@/jslib/common/src/models/domain/storageOptions";
|
||||
|
||||
import { DirectoryType } from "@/src/enums/directoryType";
|
||||
import { EntraIdConfiguration } from "@/src/models/entraIdConfiguration";
|
||||
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 {
|
||||
abstract getDirectory<IConfiguration>(type: DirectoryType): Promise<IConfiguration>;
|
||||
abstract setDirectory(
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DecodedToken } from "@/src/utils/jwt.util";
|
||||
import { DecodedToken } from "@/libs/utils/jwt.util";
|
||||
|
||||
export abstract class TokenService {
|
||||
// Token storage
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DirectoryType } from "@/src/enums/directoryType";
|
||||
import { DirectoryType } from "@/libs/enums/directoryType";
|
||||
|
||||
import { EntraIdConfiguration } from "./entraIdConfiguration";
|
||||
import { GSuiteConfiguration } from "./gsuiteConfiguration";
|
||||
@@ -1,3 +1,5 @@
|
||||
import { StateService } from "@/libs/abstractions/state.service";
|
||||
|
||||
import { ApiService } from "@/jslib/common/src/abstractions/api.service";
|
||||
import { AppIdService } from "@/jslib/common/src/abstractions/appId.service";
|
||||
import { MessagingService } from "@/jslib/common/src/abstractions/messaging.service";
|
||||
@@ -7,8 +9,6 @@ import { ApiTokenRequest } from "@/jslib/common/src/models/request/identityToken
|
||||
import { TokenRequestTwoFactor } from "@/jslib/common/src/models/request/identityToken/tokenRequestTwoFactor";
|
||||
import { IdentityTokenResponse } from "@/jslib/common/src/models/response/identityTokenResponse";
|
||||
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
|
||||
export class AuthService {
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
@@ -1,5 +1,7 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
|
||||
import { StateService } from "@/libs/abstractions/state.service";
|
||||
|
||||
import { ApiService } from "@/jslib/common/src/abstractions/api.service";
|
||||
import { AppIdService } from "@/jslib/common/src/abstractions/appId.service";
|
||||
import { MessagingService } from "@/jslib/common/src/abstractions/messaging.service";
|
||||
@@ -7,8 +9,6 @@ import { PlatformUtilsService } from "@/jslib/common/src/abstractions/platformUt
|
||||
import { Utils } from "@/jslib/common/src/misc/utils";
|
||||
import { IdentityTokenResponse } from "@/jslib/common/src/models/response/identityTokenResponse";
|
||||
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
|
||||
import { AuthService } from "./auth.service";
|
||||
|
||||
const clientId = "organization.CLIENT_ID";
|
||||
@@ -1,6 +1,6 @@
|
||||
import { GroupEntry } from "../models/groupEntry";
|
||||
import { SyncConfiguration } from "../models/syncConfiguration";
|
||||
import { UserEntry } from "../models/userEntry";
|
||||
import { GroupEntry } from "@/libs/models/groupEntry";
|
||||
import { SyncConfiguration } from "@/libs/models/syncConfiguration";
|
||||
import { UserEntry } from "@/libs/models/userEntry";
|
||||
|
||||
export abstract class BaseDirectoryService {
|
||||
protected createDirectoryQuery(filter: string) {
|
||||
@@ -1,10 +1,9 @@
|
||||
import { RequestBuilder, RequestBuilderOptions } from "@/libs/abstractions/request-builder.service";
|
||||
import { GroupEntry } from "@/libs/models/groupEntry";
|
||||
import { UserEntry } from "@/libs/models/userEntry";
|
||||
|
||||
import { OrganizationImportRequest } from "@/jslib/common/src/models/request/organizationImportRequest";
|
||||
|
||||
import { GroupEntry } from "@/src/models/groupEntry";
|
||||
import { UserEntry } from "@/src/models/userEntry";
|
||||
|
||||
import { RequestBuilder, RequestBuilderOptions } from "../abstractions/request-builder.service";
|
||||
|
||||
import { batchSize } from "./sync.service";
|
||||
|
||||
/**
|
||||
@@ -1,12 +1,12 @@
|
||||
import { RequestBuilderOptions } from "@/libs/abstractions/request-builder.service";
|
||||
import { UserEntry } from "@/libs/models/userEntry";
|
||||
|
||||
import { GetUniqueString } from "@/jslib/common/spec/utils";
|
||||
|
||||
import { UserEntry } from "@/src/models/userEntry";
|
||||
|
||||
import { groupSimulator, userSimulator } from "../../utils/request-builder-helper";
|
||||
import { RequestBuilderOptions } from "../abstractions/request-builder.service";
|
||||
|
||||
import { BatchRequestBuilder } from "./batch-request-builder";
|
||||
|
||||
import { groupSimulator, userSimulator } from "@/utils/request-builder-helper";
|
||||
|
||||
describe("BatchRequestBuilder", () => {
|
||||
let batchRequestBuilder: BatchRequestBuilder;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { DirectoryFactoryService } from "@/libs/abstractions/directory-factory.service";
|
||||
import { StateService } from "@/libs/abstractions/state.service";
|
||||
import { DirectoryType } from "@/libs/enums/directoryType";
|
||||
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
|
||||
import { DirectoryFactoryService } from "../abstractions/directory-factory.service";
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
import { DirectoryType } from "../enums/directoryType";
|
||||
|
||||
import { EntraIdDirectoryService } from "./directory-services/entra-id-directory.service";
|
||||
import { GSuiteDirectoryService } from "./directory-services/gsuite-directory.service";
|
||||
import { LdapDirectoryService } from "./directory-services/ldap-directory.service";
|
||||
@@ -1,7 +1,7 @@
|
||||
import { config as dotenvConfig } from "dotenv";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { StateService } from "@/src/abstractions/state.service";
|
||||
import { StateService } from "@/libs/abstractions/state.service";
|
||||
|
||||
import { I18nService } from "../../../jslib/common/src/abstractions/i18n.service";
|
||||
import { LogService } from "../../../jslib/common/src/abstractions/log.service";
|
||||
@@ -1,11 +1,11 @@
|
||||
import { JWT } from "google-auth-library";
|
||||
import { admin_directory_v1, google } from "googleapis";
|
||||
|
||||
import { StateService } from "@/libs/abstractions/state.service";
|
||||
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
|
||||
import { StateService } from "@/src/abstractions/state.service";
|
||||
|
||||
import { DirectoryType } from "../../enums/directoryType";
|
||||
import { GroupEntry } from "../../models/groupEntry";
|
||||
import { GSuiteConfiguration } from "../../models/gsuiteConfiguration";
|
||||
@@ -1,7 +1,7 @@
|
||||
import { EnvironmentUrls } from "@/jslib/common/src/models/domain/environmentUrls";
|
||||
import { EnvironmentService as IEnvironmentService } from "@/libs/abstractions/environment.service";
|
||||
import { StateService } from "@/libs/abstractions/state.service";
|
||||
|
||||
import { EnvironmentService as IEnvironmentService } from "@/src/abstractions/environment.service";
|
||||
import { StateService } from "@/src/abstractions/state.service";
|
||||
import { EnvironmentUrls } from "@/jslib/common/src/models/domain/environmentUrls";
|
||||
|
||||
export class EnvironmentService implements IEnvironmentService {
|
||||
private readonly DEFAULT_URLS = {
|
||||
@@ -1,12 +1,12 @@
|
||||
import { RequestBuilderOptions } from "@/libs/abstractions/request-builder.service";
|
||||
import { UserEntry } from "@/libs/models/userEntry";
|
||||
|
||||
import { GetUniqueString } from "@/jslib/common/spec/utils";
|
||||
|
||||
import { UserEntry } from "@/src/models/userEntry";
|
||||
|
||||
import { groupSimulator, userSimulator } from "../../utils/request-builder-helper";
|
||||
import { RequestBuilderOptions } from "../abstractions/request-builder.service";
|
||||
|
||||
import { SingleRequestBuilder } from "./single-request-builder";
|
||||
|
||||
import { groupSimulator, userSimulator } from "@/utils/request-builder-helper";
|
||||
|
||||
describe("SingleRequestBuilder", () => {
|
||||
let singleRequestBuilder: SingleRequestBuilder;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { RequestBuilder, RequestBuilderOptions } from "@/libs/abstractions/request-builder.service";
|
||||
import { GroupEntry } from "@/libs/models/groupEntry";
|
||||
import { UserEntry } from "@/libs/models/userEntry";
|
||||
|
||||
import { OrganizationImportRequest } from "@/jslib/common/src/models/request/organizationImportRequest";
|
||||
|
||||
import { GroupEntry } from "@/src/models/groupEntry";
|
||||
import { UserEntry } from "@/src/models/userEntry";
|
||||
|
||||
import { RequestBuilder, RequestBuilderOptions } from "../abstractions/request-builder.service";
|
||||
|
||||
/**
|
||||
* This class is responsible for building small (<2k users) syncs as a single
|
||||
* request to the /import endpoint. This is done to be backwards compatible with
|
||||
@@ -1,21 +1,21 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
import { StorageService } from "@/jslib/common/src/abstractions/storage.service";
|
||||
import { EnvironmentUrls } from "@/jslib/common/src/models/domain/environmentUrls";
|
||||
|
||||
import { DirectoryType } from "@/src/enums/directoryType";
|
||||
import { EntraIdConfiguration } from "@/src/models/entraIdConfiguration";
|
||||
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 { DirectoryType } from "@/libs/enums/directoryType";
|
||||
import { EntraIdConfiguration } from "@/libs/models/entraIdConfiguration";
|
||||
import { GSuiteConfiguration } from "@/libs/models/gsuiteConfiguration";
|
||||
import { LdapConfiguration } from "@/libs/models/ldapConfiguration";
|
||||
import { OktaConfiguration } from "@/libs/models/oktaConfiguration";
|
||||
import { OneLoginConfiguration } from "@/libs/models/oneLoginConfiguration";
|
||||
import {
|
||||
SecureStorageKeysVNext as SecureStorageKeys,
|
||||
StorageKeysVNext as StorageKeys,
|
||||
StoredSecurely,
|
||||
} from "@/src/models/state.model";
|
||||
import { SyncConfiguration } from "@/src/models/syncConfiguration";
|
||||
} from "@/libs/models/state.model";
|
||||
import { SyncConfiguration } from "@/libs/models/syncConfiguration";
|
||||
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
import { StorageService } from "@/jslib/common/src/abstractions/storage.service";
|
||||
import { EnvironmentUrls } from "@/jslib/common/src/models/domain/environmentUrls";
|
||||
|
||||
import { StateServiceImplementation } from "./state.service";
|
||||
import { StateMigrationService } from "./stateMigration.service";
|
||||
@@ -1,22 +1,22 @@
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
import { StorageService } from "@/jslib/common/src/abstractions/storage.service";
|
||||
import { EnvironmentUrls } from "@/jslib/common/src/models/domain/environmentUrls";
|
||||
import { StorageOptions } from "@/jslib/common/src/models/domain/storageOptions";
|
||||
|
||||
import { StateService as StateServiceAbstraction } from "@/src/abstractions/state.service";
|
||||
import { DirectoryType } from "@/src/enums/directoryType";
|
||||
import { IConfiguration } from "@/src/models/IConfiguration";
|
||||
import { EntraIdConfiguration } from "@/src/models/entraIdConfiguration";
|
||||
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 { StateService as StateServiceAbstraction } from "@/libs/abstractions/state.service";
|
||||
import { DirectoryType } from "@/libs/enums/directoryType";
|
||||
import { IConfiguration } from "@/libs/models/IConfiguration";
|
||||
import { EntraIdConfiguration } from "@/libs/models/entraIdConfiguration";
|
||||
import { GSuiteConfiguration } from "@/libs/models/gsuiteConfiguration";
|
||||
import { LdapConfiguration } from "@/libs/models/ldapConfiguration";
|
||||
import { OktaConfiguration } from "@/libs/models/oktaConfiguration";
|
||||
import { OneLoginConfiguration } from "@/libs/models/oneLoginConfiguration";
|
||||
import {
|
||||
SecureStorageKeysVNext as SecureStorageKeys,
|
||||
StorageKeysVNext as StorageKeys,
|
||||
StoredSecurely,
|
||||
} from "@/src/models/state.model";
|
||||
import { SyncConfiguration } from "@/src/models/syncConfiguration";
|
||||
} from "@/libs/models/state.model";
|
||||
import { SyncConfiguration } from "@/libs/models/syncConfiguration";
|
||||
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
import { StorageService } from "@/jslib/common/src/abstractions/storage.service";
|
||||
import { EnvironmentUrls } from "@/jslib/common/src/models/domain/environmentUrls";
|
||||
import { StorageOptions } from "@/jslib/common/src/models/domain/storageOptions";
|
||||
|
||||
import { StateMigrationService } from "./stateMigration.service";
|
||||
|
||||
@@ -566,4 +566,4 @@ export class StateServiceImplementation implements StateServiceAbstraction {
|
||||
}
|
||||
|
||||
// Re-export the abstraction for convenience
|
||||
export { StateService } from "@/src/abstractions/state.service";
|
||||
export { StateService } from "@/libs/abstractions/state.service";
|
||||
@@ -1,15 +1,10 @@
|
||||
import { StorageService } from "@/jslib/common/src/abstractions/storage.service";
|
||||
import { HtmlStorageLocation } from "@/jslib/common/src/enums/htmlStorageLocation";
|
||||
import { StateVersion } from "@/jslib/common/src/enums/stateVersion";
|
||||
import { StorageOptions } from "@/jslib/common/src/models/domain/storageOptions";
|
||||
|
||||
import { DirectoryType } from "@/src/enums/directoryType";
|
||||
import { DirectoryConfigurations, DirectorySettings } from "@/src/models/account";
|
||||
import { EntraIdConfiguration } from "@/src/models/entraIdConfiguration";
|
||||
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 { DirectoryType } from "@/libs/enums/directoryType";
|
||||
import { DirectoryConfigurations, DirectorySettings } from "@/libs/models/account";
|
||||
import { EntraIdConfiguration } from "@/libs/models/entraIdConfiguration";
|
||||
import { GSuiteConfiguration } from "@/libs/models/gsuiteConfiguration";
|
||||
import { LdapConfiguration } from "@/libs/models/ldapConfiguration";
|
||||
import { OktaConfiguration } from "@/libs/models/oktaConfiguration";
|
||||
import { OneLoginConfiguration } from "@/libs/models/oneLoginConfiguration";
|
||||
import {
|
||||
MigrationClientKeys as ClientKeys,
|
||||
MigrationKeys as Keys,
|
||||
@@ -17,8 +12,13 @@ import {
|
||||
SecureStorageKeysMigration as SecureStorageKeys,
|
||||
SecureStorageKeysVNext,
|
||||
StorageKeysVNext,
|
||||
} from "@/src/models/state.model";
|
||||
import { SyncConfiguration } from "@/src/models/syncConfiguration";
|
||||
} from "@/libs/models/state.model";
|
||||
import { SyncConfiguration } from "@/libs/models/syncConfiguration";
|
||||
|
||||
import { StorageService } from "@/jslib/common/src/abstractions/storage.service";
|
||||
import { HtmlStorageLocation } from "@/jslib/common/src/enums/htmlStorageLocation";
|
||||
import { StateVersion } from "@/jslib/common/src/enums/stateVersion";
|
||||
import { StorageOptions } from "@/jslib/common/src/models/domain/storageOptions";
|
||||
|
||||
export class StateMigrationService {
|
||||
constructor(
|
||||
@@ -1,15 +1,15 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { DirectoryFactoryService } from "@/libs/abstractions/directory-factory.service";
|
||||
import { StateService } from "@/libs/abstractions/state.service";
|
||||
import { DirectoryType } from "@/libs/enums/directoryType";
|
||||
|
||||
import { ApiService } from "@/jslib/common/src/abstractions/api.service";
|
||||
import { CryptoFunctionService } from "@/jslib/common/src/abstractions/cryptoFunction.service";
|
||||
import { MessagingService } from "@/jslib/common/src/abstractions/messaging.service";
|
||||
|
||||
import { I18nService } from "../../jslib/common/src/abstractions/i18n.service";
|
||||
import { LogService } from "../../jslib/common/src/abstractions/log.service";
|
||||
import { getLdapConfiguration, getSyncConfiguration } from "../../utils/openldap/config-fixtures";
|
||||
import { DirectoryFactoryService } from "../abstractions/directory-factory.service";
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
import { DirectoryType } from "../enums/directoryType";
|
||||
|
||||
import { BatchRequestBuilder } from "./batch-request-builder";
|
||||
import { LdapDirectoryService } from "./directory-services/ldap-directory.service";
|
||||
@@ -17,6 +17,7 @@ import { SingleRequestBuilder } from "./single-request-builder";
|
||||
import { SyncService } from "./sync.service";
|
||||
import * as constants from "./sync.service";
|
||||
|
||||
import { getLdapConfiguration, getSyncConfiguration } from "@/utils/openldap/config-fixtures";
|
||||
import { groupFixtures } from "@/utils/openldap/group-fixtures";
|
||||
import { userFixtures } from "@/utils/openldap/user-fixtures";
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { DirectoryFactoryService } from "@/libs/abstractions/directory-factory.service";
|
||||
import { StateService } from "@/libs/abstractions/state.service";
|
||||
import { DirectoryType } from "@/libs/enums/directoryType";
|
||||
|
||||
import { CryptoFunctionService } from "@/jslib/common/src/abstractions/cryptoFunction.service";
|
||||
import { MessagingService } from "@/jslib/common/src/abstractions/messaging.service";
|
||||
import { OrganizationImportRequest } from "@/jslib/common/src/models/request/organizationImportRequest";
|
||||
import { ApiService } from "@/jslib/common/src/services/api.service";
|
||||
|
||||
import { getSyncConfiguration } from "../../utils/openldap/config-fixtures";
|
||||
import { DirectoryFactoryService } from "../abstractions/directory-factory.service";
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
import { DirectoryType } from "../enums/directoryType";
|
||||
|
||||
import { BatchRequestBuilder } from "./batch-request-builder";
|
||||
import { LdapDirectoryService } from "./directory-services/ldap-directory.service";
|
||||
import { I18nService } from "./i18n.service";
|
||||
@@ -17,6 +16,7 @@ import { SingleRequestBuilder } from "./single-request-builder";
|
||||
import { SyncService } from "./sync.service";
|
||||
import * as constants from "./sync.service";
|
||||
|
||||
import { getSyncConfiguration } from "@/utils/openldap/config-fixtures";
|
||||
import { groupFixtures } from "@/utils/openldap/group-fixtures";
|
||||
import { userFixtures } from "@/utils/openldap/user-fixtures";
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
import { DirectoryFactoryService } from "@/libs/abstractions/directory-factory.service";
|
||||
import { StateService } from "@/libs/abstractions/state.service";
|
||||
import { DirectoryType } from "@/libs/enums/directoryType";
|
||||
import { GroupEntry } from "@/libs/models/groupEntry";
|
||||
import { SyncConfiguration } from "@/libs/models/syncConfiguration";
|
||||
import { UserEntry } from "@/libs/models/userEntry";
|
||||
|
||||
import { ApiService } from "@/jslib/common/src/abstractions/api.service";
|
||||
import { CryptoFunctionService } from "@/jslib/common/src/abstractions/cryptoFunction.service";
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
@@ -5,13 +12,6 @@ import { MessagingService } from "@/jslib/common/src/abstractions/messaging.serv
|
||||
import { Utils } from "@/jslib/common/src/misc/utils";
|
||||
import { OrganizationImportRequest } from "@/jslib/common/src/models/request/organizationImportRequest";
|
||||
|
||||
import { DirectoryFactoryService } from "../abstractions/directory-factory.service";
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
import { DirectoryType } from "../enums/directoryType";
|
||||
import { GroupEntry } from "../models/groupEntry";
|
||||
import { SyncConfiguration } from "../models/syncConfiguration";
|
||||
import { UserEntry } from "../models/userEntry";
|
||||
|
||||
import { BatchRequestBuilder } from "./batch-request-builder";
|
||||
import { SingleRequestBuilder } from "./single-request-builder";
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { StorageService } from "@/jslib/common/src/abstractions/storage.service";
|
||||
|
||||
import { TokenService as ITokenService } from "@/src/abstractions/token.service";
|
||||
import { TokenService as ITokenService } from "@/libs/abstractions/token.service";
|
||||
import {
|
||||
DecodedToken,
|
||||
decodeJwt,
|
||||
tokenNeedsRefresh as checkTokenNeedsRefresh,
|
||||
} from "@/src/utils/jwt.util";
|
||||
} from "@/libs/utils/jwt.util";
|
||||
|
||||
import { StorageService } from "@/jslib/common/src/abstractions/storage.service";
|
||||
|
||||
export class TokenService implements ITokenService {
|
||||
// Storage keys
|
||||
@@ -1,11 +1,11 @@
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
import { Entry } from "@/libs/models/entry";
|
||||
import { LdapConfiguration } from "@/libs/models/ldapConfiguration";
|
||||
import { SimResult } from "@/libs/models/simResult";
|
||||
import { SyncConfiguration } from "@/libs/models/syncConfiguration";
|
||||
import { UserEntry } from "@/libs/models/userEntry";
|
||||
import { SyncService } from "@/libs/services/sync.service";
|
||||
|
||||
import { Entry } from "./models/entry";
|
||||
import { LdapConfiguration } from "./models/ldapConfiguration";
|
||||
import { SimResult } from "./models/simResult";
|
||||
import { SyncConfiguration } from "./models/syncConfiguration";
|
||||
import { UserEntry } from "./models/userEntry";
|
||||
import { SyncService } from "./services/sync.service";
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
|
||||
export class ConnectorUtils {
|
||||
static async simulate(
|
||||
@@ -3,35 +3,37 @@ import { dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import * as path from "path";
|
||||
|
||||
import { DirectoryFactoryService } from "@/libs/abstractions/directory-factory.service";
|
||||
import { EnvironmentService } from "@/libs/abstractions/environment.service";
|
||||
import { StateService } from "@/libs/abstractions/state.service";
|
||||
import { TokenService } from "@/libs/abstractions/token.service";
|
||||
import { AuthService } from "@/libs/services/auth.service";
|
||||
import { BatchRequestBuilder } from "@/libs/services/batch-request-builder";
|
||||
import { DefaultDirectoryFactoryService } from "@/libs/services/directory-factory.service";
|
||||
import { EnvironmentService as EnvironmentServiceImplementation } from "@/libs/services/environment/environment.service";
|
||||
import { I18nService } from "@/libs/services/i18n.service";
|
||||
import { KeytarSecureStorageService } from "@/libs/services/keytarSecureStorage.service";
|
||||
import { LowdbStorageService } from "@/libs/services/lowdbStorage.service";
|
||||
import { SingleRequestBuilder } from "@/libs/services/single-request-builder";
|
||||
import { StateServiceImplementation } from "@/libs/services/state-service/state.service";
|
||||
import { StateMigrationService } from "@/libs/services/state-service/stateMigration.service";
|
||||
import { SyncService } from "@/libs/services/sync.service";
|
||||
import { TokenService as TokenServiceImplementation } from "@/libs/services/token/token.service";
|
||||
|
||||
import { StorageService as StorageServiceAbstraction } from "@/jslib/common/src/abstractions/storage.service";
|
||||
import { ClientType } from "@/jslib/common/src/enums/clientType";
|
||||
import { LogLevelType } from "@/jslib/common/src/enums/logLevelType";
|
||||
import { AppIdService } from "@/jslib/common/src/services/appId.service";
|
||||
import { NoopMessagingService } from "@/jslib/common/src/services/noopMessaging.service";
|
||||
import { CliPlatformUtilsService } from "@/jslib/node/src/cli/services/cliPlatformUtils.service";
|
||||
import { ConsoleLogService } from "@/jslib/node/src/cli/services/consoleLog.service";
|
||||
import { NodeApiService } from "@/jslib/node/src/services/nodeApi.service";
|
||||
import { NodeCryptoFunctionService } from "@/jslib/node/src/services/nodeCryptoFunction.service";
|
||||
|
||||
import { CliPlatformUtilsService } from "@/src-cli/cli/services/cliPlatformUtils.service";
|
||||
import { ConsoleLogService } from "@/src-cli/cli/services/consoleLog.service";
|
||||
import { NodeApiService } from "@/src-cli/services/node/nodeApi.service";
|
||||
import { NodeCryptoFunctionService } from "@/src-cli/services/node/nodeCryptoFunction.service";
|
||||
|
||||
import packageJson from "../package.json";
|
||||
|
||||
import { DirectoryFactoryService } from "./abstractions/directory-factory.service";
|
||||
import { EnvironmentService } from "./abstractions/environment.service";
|
||||
import { StateService } from "./abstractions/state.service";
|
||||
import { TokenService } from "./abstractions/token.service";
|
||||
import { Program } from "./program";
|
||||
import { AuthService } from "./services/auth.service";
|
||||
import { BatchRequestBuilder } from "./services/batch-request-builder";
|
||||
import { DefaultDirectoryFactoryService } from "./services/directory-factory.service";
|
||||
import { EnvironmentService as EnvironmentServiceImplementation } from "./services/environment/environment.service";
|
||||
import { I18nService } from "./services/i18n.service";
|
||||
import { KeytarSecureStorageService } from "./services/keytarSecureStorage.service";
|
||||
import { LowdbStorageService } from "./services/lowdbStorage.service";
|
||||
import { SingleRequestBuilder } from "./services/single-request-builder";
|
||||
import { StateServiceImplementation } from "./services/state-service/state.service";
|
||||
import { StateMigrationService } from "./services/state-service/stateMigration.service";
|
||||
import { SyncService } from "./services/sync.service";
|
||||
import { TokenService as TokenServiceImplementation } from "./services/token/token.service";
|
||||
|
||||
// ESM __dirname polyfill for Node 20
|
||||
|
||||
116
src-cli/cli/baseProgram.ts
Normal file
116
src-cli/cli/baseProgram.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import * as chalk from "chalk";
|
||||
|
||||
import { StateService } from "@/jslib/common/src/abstractions/state.service";
|
||||
|
||||
import { Response } from "@/src-cli/cli/models/response";
|
||||
import { ListResponse } from "@/src-cli/cli/models/response/listResponse";
|
||||
import { MessageResponse } from "@/src-cli/cli/models/response/messageResponse";
|
||||
import { StringResponse } from "@/src-cli/cli/models/response/stringResponse";
|
||||
|
||||
export abstract class BaseProgram {
|
||||
constructor(
|
||||
protected stateService: StateService,
|
||||
private writeLn: (s: string, finalLine: boolean, error: boolean) => void,
|
||||
) {}
|
||||
|
||||
protected processResponse(
|
||||
response: Response,
|
||||
exitImmediately = false,
|
||||
dataProcessor: () => string = null,
|
||||
) {
|
||||
if (!response.success) {
|
||||
if (process.env.BW_QUIET !== "true") {
|
||||
if (process.env.BW_RESPONSE === "true") {
|
||||
this.writeLn(this.getJson(response), true, false);
|
||||
} else {
|
||||
this.writeLn(chalk.redBright(response.message), true, true);
|
||||
}
|
||||
}
|
||||
const exitCode = process.env.BW_CLEANEXIT ? 0 : 1;
|
||||
if (exitImmediately) {
|
||||
process.exit(exitCode);
|
||||
} else {
|
||||
process.exitCode = exitCode;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.env.BW_RESPONSE === "true") {
|
||||
this.writeLn(this.getJson(response), true, false);
|
||||
} else if (response.data != null) {
|
||||
let out: string = dataProcessor != null ? dataProcessor() : null;
|
||||
if (out == null) {
|
||||
if (response.data.object === "string") {
|
||||
const data = (response.data as StringResponse).data;
|
||||
if (data != null) {
|
||||
out = data;
|
||||
}
|
||||
} else if (response.data.object === "list") {
|
||||
out = this.getJson((response.data as ListResponse).data);
|
||||
} else if (response.data.object === "message") {
|
||||
out = this.getMessage(response);
|
||||
} else {
|
||||
out = this.getJson(response.data);
|
||||
}
|
||||
}
|
||||
|
||||
if (out != null && process.env.BW_QUIET !== "true") {
|
||||
this.writeLn(out, true, false);
|
||||
}
|
||||
}
|
||||
if (exitImmediately) {
|
||||
process.exit(0);
|
||||
} else {
|
||||
process.exitCode = 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected getJson(obj: any): string {
|
||||
if (process.env.BW_PRETTY === "true") {
|
||||
return JSON.stringify(obj, null, " ");
|
||||
} else {
|
||||
return JSON.stringify(obj);
|
||||
}
|
||||
}
|
||||
|
||||
protected getMessage(response: Response): string {
|
||||
const message = response.data as MessageResponse;
|
||||
if (process.env.BW_RAW === "true") {
|
||||
return message.raw;
|
||||
}
|
||||
|
||||
let out = "";
|
||||
if (message.title != null) {
|
||||
if (message.noColor) {
|
||||
out = message.title;
|
||||
} else {
|
||||
out = chalk.greenBright(message.title);
|
||||
}
|
||||
}
|
||||
if (message.message != null) {
|
||||
if (message.title != null) {
|
||||
out += "\n";
|
||||
}
|
||||
out += message.message;
|
||||
}
|
||||
return out.trim() === "" ? null : out;
|
||||
}
|
||||
|
||||
protected async exitIfAuthed() {
|
||||
const authed = await this.stateService.getIsAuthenticated();
|
||||
if (authed) {
|
||||
const organizationId = await this.stateService.getEntityId();
|
||||
this.processResponse(
|
||||
Response.error("You are already logged in to" + organizationId + "."),
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected async exitIfNotAuthed() {
|
||||
const authed = await this.stateService.getIsAuthenticated();
|
||||
if (!authed) {
|
||||
this.processResponse(Response.error("You are not logged in."), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
104
src-cli/cli/commands/update.command.ts
Normal file
104
src-cli/cli/commands/update.command.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import * as fetch from "node-fetch";
|
||||
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@/jslib/common/src/abstractions/platformUtils.service";
|
||||
|
||||
import { Response } from "@/src-cli/cli/models/response";
|
||||
import { MessageResponse } from "@/src-cli/cli/models/response/messageResponse";
|
||||
|
||||
export class UpdateCommand {
|
||||
inPkg = false;
|
||||
|
||||
constructor(
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private repoName: string,
|
||||
private executableName: string,
|
||||
private showExtendedMessage: boolean,
|
||||
) {
|
||||
this.inPkg = !!(process as any).pkg;
|
||||
}
|
||||
|
||||
async run(): Promise<Response> {
|
||||
const currentVersion = await this.platformUtilsService.getApplicationVersion();
|
||||
|
||||
const response = await fetch.default(
|
||||
"https://api.github.com/repos/bitwarden/" + this.repoName + "/releases/latest",
|
||||
);
|
||||
if (response.status === 200) {
|
||||
const responseJson = await response.json();
|
||||
const res = new MessageResponse(null, null);
|
||||
|
||||
const tagName: string = responseJson.tag_name;
|
||||
if (tagName === "v" + currentVersion) {
|
||||
res.title = "No update available.";
|
||||
res.noColor = true;
|
||||
return Response.success(res);
|
||||
}
|
||||
|
||||
let downloadUrl: string = null;
|
||||
if (responseJson.assets != null) {
|
||||
for (const a of responseJson.assets) {
|
||||
const download: string = a.browser_download_url;
|
||||
if (download == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (download.indexOf(".zip") === -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
process.platform === "win32" &&
|
||||
download.indexOf(this.executableName + "-windows") > -1
|
||||
) {
|
||||
downloadUrl = download;
|
||||
break;
|
||||
} else if (
|
||||
process.platform === "darwin" &&
|
||||
download.indexOf(this.executableName + "-macos") > -1
|
||||
) {
|
||||
downloadUrl = download;
|
||||
break;
|
||||
} else if (
|
||||
process.platform === "linux" &&
|
||||
download.indexOf(this.executableName + "-linux") > -1
|
||||
) {
|
||||
downloadUrl = download;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.title = "A new version is available: " + tagName;
|
||||
if (downloadUrl == null) {
|
||||
downloadUrl = "https://github.com/bitwarden/" + this.repoName + "/releases";
|
||||
} else {
|
||||
res.raw = downloadUrl;
|
||||
}
|
||||
res.message = "";
|
||||
if (responseJson.body != null && responseJson.body !== "") {
|
||||
res.message = responseJson.body + "\n\n";
|
||||
}
|
||||
|
||||
res.message += "You can download this update at " + downloadUrl;
|
||||
|
||||
if (this.showExtendedMessage) {
|
||||
if (this.inPkg) {
|
||||
res.message +=
|
||||
"\n\nIf you installed this CLI through a package manager " +
|
||||
"you should probably update using its update command instead.";
|
||||
} else {
|
||||
res.message +=
|
||||
"\n\nIf you installed this CLI through NPM " +
|
||||
"you should update using `npm install -g @bitwarden/" +
|
||||
this.repoName +
|
||||
"`";
|
||||
}
|
||||
}
|
||||
return Response.success(res);
|
||||
} else {
|
||||
return Response.error("Error contacting update API: " + response.status);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
src-cli/cli/models/response.ts
Normal file
50
src-cli/cli/models/response.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { BaseResponse } from "./response/baseResponse";
|
||||
|
||||
export class Response {
|
||||
static error(error: any, data?: any): Response {
|
||||
const res = new Response();
|
||||
res.success = false;
|
||||
if (typeof error === "string") {
|
||||
res.message = error;
|
||||
} else {
|
||||
res.message =
|
||||
error.message != null
|
||||
? error.message
|
||||
: error.toString() === "[object Object]"
|
||||
? JSON.stringify(error)
|
||||
: error.toString();
|
||||
}
|
||||
res.data = data;
|
||||
return res;
|
||||
}
|
||||
|
||||
static notFound(): Response {
|
||||
return Response.error("Not found.");
|
||||
}
|
||||
|
||||
static badRequest(message: string): Response {
|
||||
return Response.error(message);
|
||||
}
|
||||
|
||||
static multipleResults(ids: string[]): Response {
|
||||
let msg =
|
||||
"More than one result was found. Try getting a specific object by `id` instead. " +
|
||||
"The following objects were found:";
|
||||
ids.forEach((id) => {
|
||||
msg += "\n" + id;
|
||||
});
|
||||
return Response.error(msg, ids);
|
||||
}
|
||||
|
||||
static success(data?: BaseResponse): Response {
|
||||
const res = new Response();
|
||||
res.success = true;
|
||||
res.data = data;
|
||||
return res;
|
||||
}
|
||||
|
||||
success: boolean;
|
||||
message: string;
|
||||
errorCode: number;
|
||||
data: BaseResponse;
|
||||
}
|
||||
3
src-cli/cli/models/response/baseResponse.ts
Normal file
3
src-cli/cli/models/response/baseResponse.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface BaseResponse {
|
||||
object: string;
|
||||
}
|
||||
11
src-cli/cli/models/response/listResponse.ts
Normal file
11
src-cli/cli/models/response/listResponse.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { BaseResponse } from "./baseResponse";
|
||||
|
||||
export class ListResponse implements BaseResponse {
|
||||
object: string;
|
||||
data: BaseResponse[];
|
||||
|
||||
constructor(data: BaseResponse[]) {
|
||||
this.object = "list";
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
15
src-cli/cli/models/response/messageResponse.ts
Normal file
15
src-cli/cli/models/response/messageResponse.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { BaseResponse } from "./baseResponse";
|
||||
|
||||
export class MessageResponse implements BaseResponse {
|
||||
object: string;
|
||||
title: string;
|
||||
message: string;
|
||||
raw: string;
|
||||
noColor = false;
|
||||
|
||||
constructor(title: string, message: string) {
|
||||
this.object = "message";
|
||||
this.title = title;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
11
src-cli/cli/models/response/stringResponse.ts
Normal file
11
src-cli/cli/models/response/stringResponse.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { BaseResponse } from "./baseResponse";
|
||||
|
||||
export class StringResponse implements BaseResponse {
|
||||
object: string;
|
||||
data: string;
|
||||
|
||||
constructor(data: string) {
|
||||
this.object = "string";
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
160
src-cli/cli/services/cliPlatformUtils.service.ts
Normal file
160
src-cli/cli/services/cliPlatformUtils.service.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import { PlatformUtilsService } from "@/jslib/common/src/abstractions/platformUtils.service";
|
||||
import { ClientType } from "@/jslib/common/src/enums/clientType";
|
||||
import { DeviceType } from "@/jslib/common/src/enums/deviceType";
|
||||
import { ThemeType } from "@/jslib/common/src/enums/themeType";
|
||||
|
||||
export class CliPlatformUtilsService implements PlatformUtilsService {
|
||||
clientType: ClientType;
|
||||
|
||||
private deviceCache: DeviceType = null;
|
||||
|
||||
constructor(
|
||||
clientType: ClientType,
|
||||
private packageJson: any,
|
||||
) {
|
||||
this.clientType = clientType;
|
||||
}
|
||||
|
||||
getDevice(): DeviceType {
|
||||
if (!this.deviceCache) {
|
||||
switch (process.platform) {
|
||||
case "win32":
|
||||
this.deviceCache = DeviceType.WindowsDesktop;
|
||||
break;
|
||||
case "darwin":
|
||||
this.deviceCache = DeviceType.MacOsDesktop;
|
||||
break;
|
||||
case "linux":
|
||||
default:
|
||||
this.deviceCache = DeviceType.LinuxDesktop;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return this.deviceCache;
|
||||
}
|
||||
|
||||
getDeviceString(): string {
|
||||
const device = DeviceType[this.getDevice()].toLowerCase();
|
||||
return device.replace("desktop", "");
|
||||
}
|
||||
|
||||
getClientType() {
|
||||
return this.clientType;
|
||||
}
|
||||
|
||||
isFirefox() {
|
||||
return false;
|
||||
}
|
||||
|
||||
isChrome() {
|
||||
return false;
|
||||
}
|
||||
|
||||
isEdge() {
|
||||
return false;
|
||||
}
|
||||
|
||||
isOpera() {
|
||||
return false;
|
||||
}
|
||||
|
||||
isVivaldi() {
|
||||
return false;
|
||||
}
|
||||
|
||||
isSafari() {
|
||||
return false;
|
||||
}
|
||||
|
||||
isMacAppStore() {
|
||||
return false;
|
||||
}
|
||||
|
||||
isViewOpen() {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
launchUri(_uri: string, _options?: any): void {
|
||||
throw new Error("Not implemented.");
|
||||
}
|
||||
|
||||
saveFile(win: Window, blobData: any, blobOptions: any, fileName: string): void {
|
||||
throw new Error("Not implemented.");
|
||||
}
|
||||
|
||||
getApplicationVersion(): Promise<string> {
|
||||
return Promise.resolve(this.packageJson.version);
|
||||
}
|
||||
|
||||
getApplicationVersionSync(): string {
|
||||
return this.packageJson.version;
|
||||
}
|
||||
|
||||
supportsWebAuthn(win: Window) {
|
||||
return false;
|
||||
}
|
||||
|
||||
supportsDuo(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
showToast(
|
||||
type: "error" | "success" | "warning" | "info",
|
||||
title: string,
|
||||
text: string | string[],
|
||||
options?: any,
|
||||
): void {
|
||||
throw new Error("Not implemented.");
|
||||
}
|
||||
|
||||
showDialog(
|
||||
text: string,
|
||||
title?: string,
|
||||
confirmText?: string,
|
||||
cancelText?: string,
|
||||
type?: string,
|
||||
): Promise<boolean> {
|
||||
throw new Error("Not implemented.");
|
||||
}
|
||||
|
||||
isDev(): boolean {
|
||||
return process.env.BWCLI_ENV === "development";
|
||||
}
|
||||
|
||||
isSelfHost(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
copyToClipboard(text: string, options?: any): void {
|
||||
throw new Error("Not implemented.");
|
||||
}
|
||||
|
||||
readFromClipboard(options?: any): Promise<string> {
|
||||
throw new Error("Not implemented.");
|
||||
}
|
||||
|
||||
supportsBiometric(): Promise<boolean> {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
authenticateBiometric(): Promise<boolean> {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
getDefaultSystemTheme() {
|
||||
return Promise.resolve(ThemeType.Light as ThemeType.Light | ThemeType.Dark);
|
||||
}
|
||||
|
||||
onDefaultSystemThemeChange() {
|
||||
/* noop */
|
||||
}
|
||||
|
||||
getEffectiveTheme() {
|
||||
return Promise.resolve(ThemeType.Light);
|
||||
}
|
||||
|
||||
supportsSecureStorage(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
22
src-cli/cli/services/consoleLog.service.ts
Normal file
22
src-cli/cli/services/consoleLog.service.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { LogLevelType } from "@/jslib/common/src/enums/logLevelType";
|
||||
import { ConsoleLogService as BaseConsoleLogService } from "@/jslib/common/src/services/consoleLog.service";
|
||||
|
||||
export class ConsoleLogService extends BaseConsoleLogService {
|
||||
constructor(isDev: boolean, filter: (level: LogLevelType) => boolean = null) {
|
||||
super(isDev, filter);
|
||||
}
|
||||
|
||||
write(level: LogLevelType, message: string) {
|
||||
if (this.filter != null && this.filter(level)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.env.BW_RESPONSE === "true") {
|
||||
// eslint-disable-next-line
|
||||
console.error(message);
|
||||
return;
|
||||
}
|
||||
|
||||
super.write(level, message);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import * as program from "commander";
|
||||
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
import { Response } from "@/jslib/node/src/cli/models/response";
|
||||
import { MessageResponse } from "@/jslib/node/src/cli/models/response/messageResponse";
|
||||
import { StateService } from "@/libs/abstractions/state.service";
|
||||
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
|
||||
import { Response } from "@/src-cli/cli/models/response";
|
||||
import { MessageResponse } from "@/src-cli/cli/models/response/messageResponse";
|
||||
|
||||
export class ClearCacheCommand {
|
||||
constructor(
|
||||
@@ -1,20 +1,21 @@
|
||||
import * as program from "commander";
|
||||
|
||||
import { StateService } from "@/libs/abstractions/state.service";
|
||||
import { DirectoryType } from "@/libs/enums/directoryType";
|
||||
import { EntraIdConfiguration } from "@/libs/models/entraIdConfiguration";
|
||||
import { GSuiteConfiguration } from "@/libs/models/gsuiteConfiguration";
|
||||
import { LdapConfiguration } from "@/libs/models/ldapConfiguration";
|
||||
import { OktaConfiguration } from "@/libs/models/oktaConfiguration";
|
||||
import { OneLoginConfiguration } from "@/libs/models/oneLoginConfiguration";
|
||||
import { SyncConfiguration } from "@/libs/models/syncConfiguration";
|
||||
import { ConnectorUtils } from "@/libs/utils";
|
||||
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
import { NodeUtils } from "@/jslib/common/src/misc/nodeUtils";
|
||||
import { EnvironmentUrls } from "@/jslib/common/src/models/domain/environmentUrls";
|
||||
import { Response } from "@/jslib/node/src/cli/models/response";
|
||||
import { MessageResponse } from "@/jslib/node/src/cli/models/response/messageResponse";
|
||||
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
import { DirectoryType } from "../enums/directoryType";
|
||||
import { EntraIdConfiguration } from "../models/entraIdConfiguration";
|
||||
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 { Response } from "@/src-cli/cli/models/response";
|
||||
import { MessageResponse } from "@/src-cli/cli/models/response/messageResponse";
|
||||
|
||||
export class ConfigCommand {
|
||||
private directory: DirectoryType;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Response } from "@/jslib/node/src/cli/models/response";
|
||||
import { StringResponse } from "@/jslib/node/src/cli/models/response/stringResponse";
|
||||
import { StateService } from "@/libs/abstractions/state.service";
|
||||
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
import { Response } from "@/src-cli/cli/models/response";
|
||||
import { StringResponse } from "@/src-cli/cli/models/response/stringResponse";
|
||||
|
||||
export class LastSyncCommand {
|
||||
constructor(private stateService: StateService) {}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { AuthService } from "../abstractions/auth.service";
|
||||
import { AuthService } from "@/libs/abstractions/auth.service";
|
||||
|
||||
import { LoginCommand } from "./login.command";
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import * as inquirer from "inquirer";
|
||||
|
||||
import { Response } from "@/jslib/node/src/cli/models/response";
|
||||
import { MessageResponse } from "@/jslib/node/src/cli/models/response/messageResponse";
|
||||
import { AuthService } from "@/libs/abstractions/auth.service";
|
||||
|
||||
import { Response } from "@/src-cli/cli/models/response";
|
||||
import { MessageResponse } from "@/src-cli/cli/models/response/messageResponse";
|
||||
|
||||
import { Utils } from "../../jslib/common/src/misc/utils";
|
||||
import { AuthService } from "../abstractions/auth.service";
|
||||
|
||||
export class LoginCommand {
|
||||
private canInteract: boolean;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Response } from "@/jslib/node/src/cli/models/response";
|
||||
import { MessageResponse } from "@/jslib/node/src/cli/models/response/messageResponse";
|
||||
import { AuthService } from "@/libs/abstractions/auth.service";
|
||||
|
||||
import { AuthService } from "../abstractions/auth.service";
|
||||
import { Response } from "@/src-cli/cli/models/response";
|
||||
import { MessageResponse } from "@/src-cli/cli/models/response/messageResponse";
|
||||
|
||||
export class LogoutCommand {
|
||||
constructor(
|
||||
@@ -1,8 +1,9 @@
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
import { Response } from "@/jslib/node/src/cli/models/response";
|
||||
import { MessageResponse } from "@/jslib/node/src/cli/models/response/messageResponse";
|
||||
import { SyncService } from "@/libs/services/sync.service";
|
||||
|
||||
import { SyncService } from "../services/sync.service";
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
|
||||
import { Response } from "@/src-cli/cli/models/response";
|
||||
import { MessageResponse } from "@/src-cli/cli/models/response/messageResponse";
|
||||
|
||||
export class SyncCommand {
|
||||
constructor(
|
||||
@@ -1,11 +1,12 @@
|
||||
import * as program from "commander";
|
||||
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
import { Response } from "@/jslib/node/src/cli/models/response";
|
||||
import { TestResponse } from "@/libs/models/response/testResponse";
|
||||
import { SyncService } from "@/libs/services/sync.service";
|
||||
import { ConnectorUtils } from "@/libs/utils";
|
||||
|
||||
import { TestResponse } from "../models/response/testResponse";
|
||||
import { SyncService } from "../services/sync.service";
|
||||
import { ConnectorUtils } from "../utils";
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
|
||||
import { Response } from "@/src-cli/cli/models/response";
|
||||
|
||||
export class TestCommand {
|
||||
constructor(
|
||||
@@ -4,10 +4,11 @@ import * as chalk from "chalk";
|
||||
import { Command, OptionValues } from "commander";
|
||||
|
||||
import { Utils } from "@/jslib/common/src/misc/utils";
|
||||
import { BaseProgram } from "@/jslib/node/src/cli/baseProgram";
|
||||
import { UpdateCommand } from "@/jslib/node/src/cli/commands/update.command";
|
||||
import { Response } from "@/jslib/node/src/cli/models/response";
|
||||
import { StringResponse } from "@/jslib/node/src/cli/models/response/stringResponse";
|
||||
|
||||
import { BaseProgram } from "@/src-cli/cli/baseProgram";
|
||||
import { UpdateCommand } from "@/src-cli/cli/commands/update.command";
|
||||
import { Response } from "@/src-cli/cli/models/response";
|
||||
import { StringResponse } from "@/src-cli/cli/models/response/stringResponse";
|
||||
|
||||
import { Main } from "./bwdc";
|
||||
import { ClearCacheCommand } from "./commands/clearCache.command";
|
||||
148
src-cli/services/node/lowdbStorage.service.ts
Normal file
148
src-cli/services/node/lowdbStorage.service.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
import * as lowdb from "lowdb";
|
||||
import * as FileSync from "lowdb/adapters/FileSync";
|
||||
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
import { StorageService } from "@/jslib/common/src/abstractions/storage.service";
|
||||
import { NodeUtils } from "@/jslib/common/src/misc/nodeUtils";
|
||||
import { sequentialize } from "@/jslib/common/src/misc/sequentialize";
|
||||
import { Utils } from "@/jslib/common/src/misc/utils";
|
||||
|
||||
export class LowdbStorageService implements StorageService {
|
||||
protected dataFilePath: string;
|
||||
private db: lowdb.LowdbSync<any>;
|
||||
private defaults: any;
|
||||
private ready = false;
|
||||
|
||||
constructor(
|
||||
protected logService: LogService,
|
||||
defaults?: any,
|
||||
private dir?: string,
|
||||
private allowCache = false,
|
||||
) {
|
||||
this.defaults = defaults;
|
||||
}
|
||||
|
||||
@sequentialize(() => "lowdbStorageInit")
|
||||
async init() {
|
||||
if (this.ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.logService.info("Initializing lowdb storage service.");
|
||||
let adapter: lowdb.AdapterSync<any>;
|
||||
if (Utils.isNode && this.dir != null) {
|
||||
if (!fs.existsSync(this.dir)) {
|
||||
this.logService.warning(`Could not find dir, "${this.dir}"; creating it instead.`);
|
||||
NodeUtils.mkdirpSync(this.dir, "700");
|
||||
this.logService.info(`Created dir "${this.dir}".`);
|
||||
}
|
||||
this.dataFilePath = path.join(this.dir, "data.json");
|
||||
if (!fs.existsSync(this.dataFilePath)) {
|
||||
this.logService.warning(
|
||||
`Could not find data file, "${this.dataFilePath}"; creating it instead.`,
|
||||
);
|
||||
fs.writeFileSync(this.dataFilePath, "", { mode: 0o600 });
|
||||
fs.chmodSync(this.dataFilePath, 0o600);
|
||||
this.logService.info(`Created data file "${this.dataFilePath}" with chmod 600.`);
|
||||
} else {
|
||||
this.logService.info(`db file "${this.dataFilePath} already exists"; using existing db`);
|
||||
}
|
||||
await this.lockDbFile(() => {
|
||||
adapter = new FileSync(this.dataFilePath);
|
||||
});
|
||||
}
|
||||
try {
|
||||
this.logService.info("Attempting to create lowdb storage adapter.");
|
||||
this.db = lowdb(adapter);
|
||||
this.logService.info("Successfully created lowdb storage adapter.");
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
this.logService.warning(
|
||||
`Error creating lowdb storage adapter, "${e.message}"; emptying data file.`,
|
||||
);
|
||||
if (fs.existsSync(this.dataFilePath)) {
|
||||
const backupPath = this.dataFilePath + ".bak";
|
||||
this.logService.warning(`Writing backup of data file to ${backupPath}`);
|
||||
await fs.copyFile(this.dataFilePath, backupPath, () => {
|
||||
this.logService.warning(
|
||||
`Error while creating data file backup, "${e.message}". No backup may have been created.`,
|
||||
);
|
||||
});
|
||||
}
|
||||
adapter.write({});
|
||||
this.db = lowdb(adapter);
|
||||
} else {
|
||||
this.logService.error(`Error creating lowdb storage adapter, "${e.message}".`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.defaults != null) {
|
||||
this.lockDbFile(() => {
|
||||
this.logService.info("Writing defaults.");
|
||||
this.readForNoCache();
|
||||
this.db.defaults(this.defaults).write();
|
||||
this.logService.info("Successfully wrote defaults to db.");
|
||||
});
|
||||
}
|
||||
|
||||
this.ready = true;
|
||||
}
|
||||
|
||||
async get<T>(key: string): Promise<T> {
|
||||
await this.waitForReady();
|
||||
return this.lockDbFile(() => {
|
||||
this.readForNoCache();
|
||||
const val = this.db.get(key).value();
|
||||
this.logService.debug(`Successfully read ${key} from db`);
|
||||
if (val == null) {
|
||||
return null;
|
||||
}
|
||||
return val as T;
|
||||
});
|
||||
}
|
||||
|
||||
has(key: string): Promise<boolean> {
|
||||
return this.get(key).then((v) => v != null);
|
||||
}
|
||||
|
||||
async save(key: string, obj: any): Promise<any> {
|
||||
await this.waitForReady();
|
||||
return this.lockDbFile(() => {
|
||||
this.readForNoCache();
|
||||
this.db.set(key, obj).write();
|
||||
this.logService.debug(`Successfully wrote ${key} to db`);
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
async remove(key: string): Promise<any> {
|
||||
await this.waitForReady();
|
||||
return this.lockDbFile(() => {
|
||||
this.readForNoCache();
|
||||
this.db.unset(key).write();
|
||||
this.logService.debug(`Successfully removed ${key} from db`);
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
protected async lockDbFile<T>(action: () => T): Promise<T> {
|
||||
// Lock methods implemented in clients
|
||||
return Promise.resolve(action());
|
||||
}
|
||||
|
||||
private readForNoCache() {
|
||||
if (!this.allowCache) {
|
||||
this.db.read();
|
||||
}
|
||||
}
|
||||
|
||||
private async waitForReady() {
|
||||
if (!this.ready) {
|
||||
await this.init();
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src-cli/services/node/nodeApi.service.ts
Normal file
43
src-cli/services/node/nodeApi.service.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import * as FormData from "form-data";
|
||||
import { HttpsProxyAgent } from "https-proxy-agent";
|
||||
import * as fe from "node-fetch";
|
||||
|
||||
import { AppIdService } from "@/jslib/common/src/abstractions/appId.service";
|
||||
import { EnvironmentService } from "@/jslib/common/src/abstractions/environment.service";
|
||||
import { PlatformUtilsService } from "@/jslib/common/src/abstractions/platformUtils.service";
|
||||
import { TokenService } from "@/jslib/common/src/abstractions/token.service";
|
||||
import { ApiService } from "@/jslib/common/src/services/api.service";
|
||||
|
||||
(global as any).fetch = fe.default;
|
||||
(global as any).Request = fe.Request;
|
||||
(global as any).Response = fe.Response;
|
||||
(global as any).Headers = fe.Headers;
|
||||
(global as any).FormData = FormData;
|
||||
|
||||
export class NodeApiService extends ApiService {
|
||||
constructor(
|
||||
tokenService: TokenService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
environmentService: EnvironmentService,
|
||||
appIdService: AppIdService,
|
||||
logoutCallback: (expired: boolean) => Promise<void>,
|
||||
customUserAgent: string = null,
|
||||
) {
|
||||
super(
|
||||
tokenService,
|
||||
platformUtilsService,
|
||||
environmentService,
|
||||
appIdService,
|
||||
logoutCallback,
|
||||
customUserAgent,
|
||||
);
|
||||
}
|
||||
|
||||
nativeFetch(request: Request): Promise<Response> {
|
||||
const proxy = process.env.http_proxy || process.env.https_proxy;
|
||||
if (proxy) {
|
||||
(request as any).agent = new HttpsProxyAgent(proxy);
|
||||
}
|
||||
return fetch(request);
|
||||
}
|
||||
}
|
||||
301
src-cli/services/node/nodeCryptoFunction.service.ts
Normal file
301
src-cli/services/node/nodeCryptoFunction.service.ts
Normal file
@@ -0,0 +1,301 @@
|
||||
import * as crypto from "crypto";
|
||||
|
||||
import * as forge from "node-forge";
|
||||
|
||||
import { CryptoFunctionService } from "@/jslib/common/src/abstractions/cryptoFunction.service";
|
||||
import { Utils } from "@/jslib/common/src/misc/utils";
|
||||
import { DecryptParameters } from "@/jslib/common/src/models/domain/decryptParameters";
|
||||
import { SymmetricCryptoKey } from "@/jslib/common/src/models/domain/symmetricCryptoKey";
|
||||
|
||||
export class NodeCryptoFunctionService implements CryptoFunctionService {
|
||||
pbkdf2(
|
||||
password: string | ArrayBuffer,
|
||||
salt: string | ArrayBuffer,
|
||||
algorithm: "sha256" | "sha512",
|
||||
iterations: number,
|
||||
): Promise<ArrayBuffer> {
|
||||
const len = algorithm === "sha256" ? 32 : 64;
|
||||
const nodePassword = this.toNodeValue(password);
|
||||
const nodeSalt = this.toNodeValue(salt);
|
||||
return new Promise<ArrayBuffer>((resolve, reject) => {
|
||||
crypto.pbkdf2(nodePassword, nodeSalt, iterations, len, algorithm, (error, key) => {
|
||||
if (error != null) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(this.toArrayBuffer(key));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ref: https://tools.ietf.org/html/rfc5869
|
||||
async hkdf(
|
||||
ikm: ArrayBuffer,
|
||||
salt: string | ArrayBuffer,
|
||||
info: string | ArrayBuffer,
|
||||
outputByteSize: number,
|
||||
algorithm: "sha256" | "sha512",
|
||||
): Promise<ArrayBuffer> {
|
||||
const saltBuf = this.toArrayBuffer(salt);
|
||||
const prk = await this.hmac(ikm, saltBuf, algorithm);
|
||||
return this.hkdfExpand(prk, info, outputByteSize, algorithm);
|
||||
}
|
||||
|
||||
// ref: https://tools.ietf.org/html/rfc5869
|
||||
async hkdfExpand(
|
||||
prk: ArrayBuffer,
|
||||
info: string | ArrayBuffer,
|
||||
outputByteSize: number,
|
||||
algorithm: "sha256" | "sha512",
|
||||
): Promise<ArrayBuffer> {
|
||||
const hashLen = algorithm === "sha256" ? 32 : 64;
|
||||
if (outputByteSize > 255 * hashLen) {
|
||||
throw new Error("outputByteSize is too large.");
|
||||
}
|
||||
const prkArr = new Uint8Array(prk);
|
||||
if (prkArr.length < hashLen) {
|
||||
throw new Error("prk is too small.");
|
||||
}
|
||||
const infoBuf = this.toArrayBuffer(info);
|
||||
const infoArr = new Uint8Array(infoBuf);
|
||||
let runningOkmLength = 0;
|
||||
let previousT = new Uint8Array(0);
|
||||
const n = Math.ceil(outputByteSize / hashLen);
|
||||
const okm = new Uint8Array(n * hashLen);
|
||||
for (let i = 0; i < n; i++) {
|
||||
const t = new Uint8Array(previousT.length + infoArr.length + 1);
|
||||
t.set(previousT);
|
||||
t.set(infoArr, previousT.length);
|
||||
t.set([i + 1], t.length - 1);
|
||||
previousT = new Uint8Array(await this.hmac(t.buffer, prk, algorithm));
|
||||
okm.set(previousT, runningOkmLength);
|
||||
runningOkmLength += previousT.length;
|
||||
if (runningOkmLength >= outputByteSize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return okm.slice(0, outputByteSize).buffer;
|
||||
}
|
||||
|
||||
hash(
|
||||
value: string | ArrayBuffer,
|
||||
algorithm: "sha1" | "sha256" | "sha512" | "md5",
|
||||
): Promise<ArrayBuffer> {
|
||||
const nodeValue = this.toNodeValue(value);
|
||||
const hash = crypto.createHash(algorithm);
|
||||
hash.update(nodeValue);
|
||||
return Promise.resolve(this.toArrayBuffer(hash.digest()));
|
||||
}
|
||||
|
||||
hmac(
|
||||
value: ArrayBuffer,
|
||||
key: ArrayBuffer,
|
||||
algorithm: "sha1" | "sha256" | "sha512",
|
||||
): Promise<ArrayBuffer> {
|
||||
const nodeValue = this.toNodeBuffer(value);
|
||||
const nodeKey = this.toNodeBuffer(key);
|
||||
const hmac = crypto.createHmac(algorithm, nodeKey);
|
||||
hmac.update(nodeValue);
|
||||
return Promise.resolve(this.toArrayBuffer(hmac.digest()));
|
||||
}
|
||||
|
||||
async compare(a: ArrayBuffer, b: ArrayBuffer): Promise<boolean> {
|
||||
const key = await this.randomBytes(32);
|
||||
const mac1 = await this.hmac(a, key, "sha256");
|
||||
const mac2 = await this.hmac(b, key, "sha256");
|
||||
if (mac1.byteLength !== mac2.byteLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const arr1 = new Uint8Array(mac1);
|
||||
const arr2 = new Uint8Array(mac2);
|
||||
for (let i = 0; i < arr2.length; i++) {
|
||||
if (arr1[i] !== arr2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
hmacFast(
|
||||
value: ArrayBuffer,
|
||||
key: ArrayBuffer,
|
||||
algorithm: "sha1" | "sha256" | "sha512",
|
||||
): Promise<ArrayBuffer> {
|
||||
return this.hmac(value, key, algorithm);
|
||||
}
|
||||
|
||||
compareFast(a: ArrayBuffer, b: ArrayBuffer): Promise<boolean> {
|
||||
return this.compare(a, b);
|
||||
}
|
||||
|
||||
aesEncrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
|
||||
const nodeData = this.toNodeBuffer(data);
|
||||
const nodeIv = this.toNodeBuffer(iv);
|
||||
const nodeKey = this.toNodeBuffer(key);
|
||||
const cipher = crypto.createCipheriv("aes-256-cbc", nodeKey, nodeIv);
|
||||
const encBuf = Buffer.concat([cipher.update(nodeData), cipher.final()]);
|
||||
return Promise.resolve(this.toArrayBuffer(encBuf));
|
||||
}
|
||||
|
||||
aesDecryptFastParameters(
|
||||
data: string,
|
||||
iv: string,
|
||||
mac: string,
|
||||
key: SymmetricCryptoKey,
|
||||
): DecryptParameters<ArrayBuffer> {
|
||||
const p = new DecryptParameters<ArrayBuffer>();
|
||||
p.encKey = key.encKey;
|
||||
p.data = Utils.fromB64ToArray(data).buffer;
|
||||
p.iv = Utils.fromB64ToArray(iv).buffer;
|
||||
|
||||
const macData = new Uint8Array(p.iv.byteLength + p.data.byteLength);
|
||||
macData.set(new Uint8Array(p.iv), 0);
|
||||
macData.set(new Uint8Array(p.data), p.iv.byteLength);
|
||||
p.macData = macData.buffer;
|
||||
|
||||
if (key.macKey != null) {
|
||||
p.macKey = key.macKey;
|
||||
}
|
||||
if (mac != null) {
|
||||
p.mac = Utils.fromB64ToArray(mac).buffer;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
async aesDecryptFast(parameters: DecryptParameters<ArrayBuffer>): Promise<string> {
|
||||
const decBuf = await this.aesDecrypt(parameters.data, parameters.iv, parameters.encKey);
|
||||
return Utils.fromBufferToUtf8(decBuf);
|
||||
}
|
||||
|
||||
aesDecrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
|
||||
const nodeData = this.toNodeBuffer(data);
|
||||
const nodeIv = this.toNodeBuffer(iv);
|
||||
const nodeKey = this.toNodeBuffer(key);
|
||||
const decipher = crypto.createDecipheriv("aes-256-cbc", nodeKey, nodeIv);
|
||||
const decBuf = Buffer.concat([decipher.update(nodeData), decipher.final()]);
|
||||
return Promise.resolve(this.toArrayBuffer(decBuf));
|
||||
}
|
||||
|
||||
rsaEncrypt(
|
||||
data: ArrayBuffer,
|
||||
publicKey: ArrayBuffer,
|
||||
algorithm: "sha1" | "sha256",
|
||||
): Promise<ArrayBuffer> {
|
||||
if (algorithm === "sha256") {
|
||||
throw new Error("Node crypto does not support RSA-OAEP SHA-256");
|
||||
}
|
||||
|
||||
const pem = this.toPemPublicKey(publicKey);
|
||||
const decipher = crypto.publicEncrypt(pem, this.toNodeBuffer(data));
|
||||
return Promise.resolve(this.toArrayBuffer(decipher));
|
||||
}
|
||||
|
||||
rsaDecrypt(
|
||||
data: ArrayBuffer,
|
||||
privateKey: ArrayBuffer,
|
||||
algorithm: "sha1" | "sha256",
|
||||
): Promise<ArrayBuffer> {
|
||||
if (algorithm === "sha256") {
|
||||
throw new Error("Node crypto does not support RSA-OAEP SHA-256");
|
||||
}
|
||||
|
||||
const pem = this.toPemPrivateKey(privateKey);
|
||||
const decipher = crypto.privateDecrypt(pem, this.toNodeBuffer(data));
|
||||
return Promise.resolve(this.toArrayBuffer(decipher));
|
||||
}
|
||||
|
||||
rsaExtractPublicKey(privateKey: ArrayBuffer): Promise<ArrayBuffer> {
|
||||
const privateKeyByteString = Utils.fromBufferToByteString(privateKey);
|
||||
const privateKeyAsn1 = forge.asn1.fromDer(privateKeyByteString);
|
||||
const forgePrivateKey: any = forge.pki.privateKeyFromAsn1(privateKeyAsn1);
|
||||
const forgePublicKey = (forge.pki as any).setRsaPublicKey(forgePrivateKey.n, forgePrivateKey.e);
|
||||
const publicKeyAsn1 = forge.pki.publicKeyToAsn1(forgePublicKey);
|
||||
const publicKeyByteString = forge.asn1.toDer(publicKeyAsn1).data;
|
||||
const publicKeyArray = Utils.fromByteStringToArray(publicKeyByteString);
|
||||
return Promise.resolve(publicKeyArray.buffer);
|
||||
}
|
||||
|
||||
async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[ArrayBuffer, ArrayBuffer]> {
|
||||
return new Promise<[ArrayBuffer, ArrayBuffer]>((resolve, reject) => {
|
||||
forge.pki.rsa.generateKeyPair(
|
||||
{
|
||||
bits: length,
|
||||
workers: -1,
|
||||
e: 0x10001, // 65537
|
||||
},
|
||||
(error, keyPair) => {
|
||||
if (error != null) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const publicKeyAsn1 = forge.pki.publicKeyToAsn1(keyPair.publicKey);
|
||||
const publicKeyByteString = forge.asn1.toDer(publicKeyAsn1).getBytes();
|
||||
const publicKey = Utils.fromByteStringToArray(publicKeyByteString);
|
||||
|
||||
const privateKeyAsn1 = forge.pki.privateKeyToAsn1(keyPair.privateKey);
|
||||
const privateKeyPkcs8 = forge.pki.wrapRsaPrivateKey(privateKeyAsn1);
|
||||
const privateKeyByteString = forge.asn1.toDer(privateKeyPkcs8).getBytes();
|
||||
const privateKey = Utils.fromByteStringToArray(privateKeyByteString);
|
||||
|
||||
resolve([publicKey.buffer, privateKey.buffer]);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
randomBytes(length: number): Promise<ArrayBuffer> {
|
||||
return new Promise<ArrayBuffer>((resolve, reject) => {
|
||||
crypto.randomBytes(length, (error, bytes) => {
|
||||
if (error != null) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(this.toArrayBuffer(bytes));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private toNodeValue(value: string | ArrayBuffer): string | Buffer {
|
||||
let nodeValue: string | Buffer;
|
||||
if (typeof value === "string") {
|
||||
nodeValue = value;
|
||||
} else {
|
||||
nodeValue = this.toNodeBuffer(value);
|
||||
}
|
||||
return nodeValue;
|
||||
}
|
||||
|
||||
private toNodeBuffer(value: ArrayBuffer): Buffer {
|
||||
return Buffer.from(new Uint8Array(value) as any);
|
||||
}
|
||||
|
||||
private toArrayBuffer(value: Buffer | string | ArrayBuffer): ArrayBuffer {
|
||||
let buf: ArrayBuffer;
|
||||
if (typeof value === "string") {
|
||||
buf = Utils.fromUtf8ToArray(value).buffer;
|
||||
} else {
|
||||
buf = new Uint8Array(value).buffer;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
private toPemPrivateKey(key: ArrayBuffer): string {
|
||||
const byteString = Utils.fromBufferToByteString(key);
|
||||
const asn1 = forge.asn1.fromDer(byteString);
|
||||
const privateKey = forge.pki.privateKeyFromAsn1(asn1);
|
||||
const rsaPrivateKey = forge.pki.privateKeyToAsn1(privateKey);
|
||||
const privateKeyInfo = forge.pki.wrapRsaPrivateKey(rsaPrivateKey);
|
||||
return forge.pki.privateKeyInfoToPem(privateKeyInfo);
|
||||
}
|
||||
|
||||
private toPemPublicKey(key: ArrayBuffer): string {
|
||||
const byteString = Utils.fromBufferToByteString(key);
|
||||
const asn1 = forge.asn1.fromDer(byteString);
|
||||
const publicKey = forge.pki.publicKeyFromAsn1(asn1);
|
||||
return forge.pki.publicKeyToPem(publicKey);
|
||||
}
|
||||
}
|
||||
79
src-gui/angular/components/modal/dynamic-modal.component.ts
Normal file
79
src-gui/angular/components/modal/dynamic-modal.component.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { ConfigurableFocusTrap, ConfigurableFocusTrapFactory } from "@angular/cdk/a11y";
|
||||
import {
|
||||
AfterViewInit,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ComponentRef,
|
||||
ElementRef,
|
||||
OnDestroy,
|
||||
Type,
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
} from "@angular/core";
|
||||
|
||||
import { ModalService } from "@/src-gui/angular/services/modal.service";
|
||||
|
||||
import { ModalRef } from "./modal.ref";
|
||||
|
||||
@Component({
|
||||
selector: "app-modal",
|
||||
template: "<ng-template #modalContent></ng-template>",
|
||||
})
|
||||
export class DynamicModalComponent implements AfterViewInit, OnDestroy {
|
||||
componentRef: ComponentRef<any>;
|
||||
|
||||
@ViewChild("modalContent", { read: ViewContainerRef, static: true })
|
||||
modalContentRef: ViewContainerRef;
|
||||
|
||||
childComponentType: Type<any>;
|
||||
setComponentParameters: (component: any) => void;
|
||||
|
||||
private focusTrap: ConfigurableFocusTrap;
|
||||
|
||||
constructor(
|
||||
private modalService: ModalService,
|
||||
private cd: ChangeDetectorRef,
|
||||
private el: ElementRef<HTMLElement>,
|
||||
private focusTrapFactory: ConfigurableFocusTrapFactory,
|
||||
public modalRef: ModalRef,
|
||||
) {}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.loadChildComponent(this.childComponentType);
|
||||
if (this.setComponentParameters != null) {
|
||||
this.setComponentParameters(this.componentRef.instance);
|
||||
}
|
||||
this.cd.detectChanges();
|
||||
|
||||
this.modalRef.created(this.el.nativeElement);
|
||||
this.focusTrap = this.focusTrapFactory.create(
|
||||
this.el.nativeElement.querySelector(".modal-dialog"),
|
||||
);
|
||||
if (this.el.nativeElement.querySelector("[appAutoFocus]") == null) {
|
||||
this.focusTrap.focusFirstTabbableElementWhenReady();
|
||||
}
|
||||
}
|
||||
|
||||
loadChildComponent(componentType: Type<any>) {
|
||||
const componentFactory = this.modalService.resolveComponentFactory(componentType);
|
||||
|
||||
this.modalContentRef.clear();
|
||||
this.componentRef = this.modalContentRef.createComponent(componentFactory);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.componentRef) {
|
||||
this.componentRef.destroy();
|
||||
}
|
||||
this.focusTrap.destroy();
|
||||
}
|
||||
|
||||
close() {
|
||||
this.modalRef.close();
|
||||
}
|
||||
|
||||
getFocus() {
|
||||
const autoFocusEl = this.el.nativeElement.querySelector("[appAutoFocus]") as HTMLElement;
|
||||
autoFocusEl?.focus();
|
||||
}
|
||||
}
|
||||
20
src-gui/angular/components/modal/modal-injector.ts
Normal file
20
src-gui/angular/components/modal/modal-injector.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { InjectOptions, Injector, ProviderToken } from "@angular/core";
|
||||
|
||||
export class ModalInjector implements Injector {
|
||||
constructor(
|
||||
private _parentInjector: Injector,
|
||||
private _additionalTokens: WeakMap<any, any>,
|
||||
) {}
|
||||
|
||||
get<T>(
|
||||
token: ProviderToken<T>,
|
||||
notFoundValue: undefined,
|
||||
options: InjectOptions & { optional?: false },
|
||||
): T;
|
||||
get<T>(token: ProviderToken<T>, notFoundValue: null, options: InjectOptions): T;
|
||||
get<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions): T;
|
||||
get(token: any, notFoundValue?: any): any;
|
||||
get(token: any, notFoundValue?: any, flags?: any): any {
|
||||
return this._additionalTokens.get(token) ?? this._parentInjector.get<any>(token, notFoundValue);
|
||||
}
|
||||
}
|
||||
49
src-gui/angular/components/modal/modal.ref.ts
Normal file
49
src-gui/angular/components/modal/modal.ref.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { lastValueFrom, Observable, Subject } from "rxjs";
|
||||
|
||||
export class ModalRef {
|
||||
onCreated: Observable<HTMLElement>; // Modal added to the DOM.
|
||||
onClose: Observable<any>; // Initiated close.
|
||||
onClosed: Observable<any>; // Modal was closed (Remove element from DOM)
|
||||
onShow: Observable<void>; // Start showing modal
|
||||
onShown: Observable<void>; // Modal is fully visible
|
||||
|
||||
private readonly _onCreated = new Subject<HTMLElement>();
|
||||
private readonly _onClose = new Subject<any>();
|
||||
private readonly _onClosed = new Subject<any>();
|
||||
private readonly _onShow = new Subject<void>();
|
||||
private readonly _onShown = new Subject<void>();
|
||||
private lastResult: any;
|
||||
|
||||
constructor() {
|
||||
this.onCreated = this._onCreated.asObservable();
|
||||
this.onClose = this._onClose.asObservable();
|
||||
this.onClosed = this._onClosed.asObservable();
|
||||
this.onShow = this._onShow.asObservable();
|
||||
this.onShown = this._onShow.asObservable();
|
||||
}
|
||||
|
||||
show() {
|
||||
this._onShow.next();
|
||||
}
|
||||
|
||||
shown() {
|
||||
this._onShown.next();
|
||||
}
|
||||
|
||||
close(result?: any) {
|
||||
this.lastResult = result;
|
||||
this._onClose.next(result);
|
||||
}
|
||||
|
||||
closed() {
|
||||
this._onClosed.next(this.lastResult);
|
||||
}
|
||||
|
||||
created(el: HTMLElement) {
|
||||
this._onCreated.next(el);
|
||||
}
|
||||
|
||||
onClosedPromise(): Promise<any> {
|
||||
return lastValueFrom(this.onClosed);
|
||||
}
|
||||
}
|
||||
101
src-gui/angular/components/toastr.component.ts
Normal file
101
src-gui/angular/components/toastr.component.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, ModuleWithProviders, NgModule } from "@angular/core";
|
||||
import { DefaultNoComponentGlobalConfig, GlobalConfig, Toast, TOAST_CONFIG } from "ngx-toastr";
|
||||
|
||||
@Component({
|
||||
selector: "[toast-component2]",
|
||||
template: `
|
||||
@if (options().closeButton) {
|
||||
<button (click)="remove()" type="button" class="toast-close-button" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
}
|
||||
<div class="icon">
|
||||
<i></i>
|
||||
</div>
|
||||
<div>
|
||||
@if (title()) {
|
||||
<div [class]="options().titleClass" [attr.aria-label]="title()">
|
||||
{{ title() }}
|
||||
@if (duplicatesCount) {
|
||||
[{{ duplicatesCount + 1 }}]
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (message() && options().enableHtml) {
|
||||
<div
|
||||
role="alertdialog"
|
||||
aria-live="polite"
|
||||
[class]="options().messageClass"
|
||||
[innerHTML]="message()"
|
||||
></div>
|
||||
}
|
||||
@if (message() && !options().enableHtml) {
|
||||
<div
|
||||
role="alertdialog"
|
||||
aria-live="polite"
|
||||
[class]="options().messageClass"
|
||||
[attr.aria-label]="message()"
|
||||
>
|
||||
{{ message() }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (options().progressBar) {
|
||||
<div>
|
||||
<div class="toast-progress" [style.width]="width + '%'"></div>
|
||||
</div>
|
||||
}
|
||||
`,
|
||||
styles: `
|
||||
:host {
|
||||
&.toast-in {
|
||||
animation: toast-animation var(--animation-duration) var(--animation-easing);
|
||||
}
|
||||
|
||||
&.toast-out {
|
||||
animation: toast-animation var(--animation-duration) var(--animation-easing) reverse
|
||||
forwards;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes toast-animation {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
`,
|
||||
preserveWhitespaces: false,
|
||||
standalone: false,
|
||||
})
|
||||
export class BitwardenToast extends Toast {}
|
||||
|
||||
export const BitwardenToastGlobalConfig: GlobalConfig = {
|
||||
...DefaultNoComponentGlobalConfig,
|
||||
toastComponent: BitwardenToast,
|
||||
};
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
declarations: [BitwardenToast],
|
||||
exports: [BitwardenToast],
|
||||
})
|
||||
export class BitwardenToastModule {
|
||||
static forRoot(config: Partial<GlobalConfig> = {}): ModuleWithProviders<BitwardenToastModule> {
|
||||
return {
|
||||
ngModule: BitwardenToastModule,
|
||||
providers: [
|
||||
{
|
||||
provide: TOAST_CONFIG,
|
||||
useValue: {
|
||||
default: BitwardenToastGlobalConfig,
|
||||
config: config,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
27
src-gui/angular/directives/a11y-title.directive.ts
Normal file
27
src-gui/angular/directives/a11y-title.directive.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Directive, ElementRef, Input, Renderer2 } from "@angular/core";
|
||||
|
||||
@Directive({
|
||||
selector: "[appA11yTitle]",
|
||||
standalone: false,
|
||||
})
|
||||
export class A11yTitleDirective {
|
||||
@Input() set appA11yTitle(title: string) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
private title: string;
|
||||
|
||||
constructor(
|
||||
private el: ElementRef,
|
||||
private renderer: Renderer2,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (!this.el.nativeElement.hasAttribute("title")) {
|
||||
this.renderer.setAttribute(this.el.nativeElement, "title", this.title);
|
||||
}
|
||||
if (!this.el.nativeElement.hasAttribute("aria-label")) {
|
||||
this.renderer.setAttribute(this.el.nativeElement, "aria-label", this.title);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
src-gui/angular/directives/api-action.directive.ts
Normal file
50
src-gui/angular/directives/api-action.directive.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Directive, ElementRef, Input, OnChanges } from "@angular/core";
|
||||
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
import { ErrorResponse } from "@/jslib/common/src/models/response/errorResponse";
|
||||
|
||||
import { ValidationService } from "@/src-gui/angular/services/validation.service";
|
||||
|
||||
/**
|
||||
* Provides error handling, in particular for any error returned by the server in an api call.
|
||||
* Attach it to a <form> element and provide the name of the class property that will hold the api call promise.
|
||||
* e.g. <form [appApiAction]="this.formPromise">
|
||||
* Any errors/rejections that occur will be intercepted and displayed as error toasts.
|
||||
*/
|
||||
@Directive({
|
||||
selector: "[appApiAction]",
|
||||
standalone: false,
|
||||
})
|
||||
export class ApiActionDirective implements OnChanges {
|
||||
@Input() appApiAction: Promise<any>;
|
||||
|
||||
constructor(
|
||||
private el: ElementRef,
|
||||
private validationService: ValidationService,
|
||||
private logService: LogService,
|
||||
) {}
|
||||
|
||||
ngOnChanges(changes: any) {
|
||||
if (this.appApiAction == null || this.appApiAction.then == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.el.nativeElement.loading = true;
|
||||
|
||||
this.appApiAction.then(
|
||||
(response: any) => {
|
||||
this.el.nativeElement.loading = false;
|
||||
},
|
||||
(e: any) => {
|
||||
this.el.nativeElement.loading = false;
|
||||
|
||||
if ((e as ErrorResponse).captchaRequired) {
|
||||
this.logService.error("Captcha required error response: " + e.getSingleMessage());
|
||||
return;
|
||||
}
|
||||
this.logService?.error(`Received API exception: ${e}`);
|
||||
this.validationService.showError(e);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
31
src-gui/angular/directives/autofocus.directive.ts
Normal file
31
src-gui/angular/directives/autofocus.directive.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Directive, ElementRef, Input, NgZone } from "@angular/core";
|
||||
import { take } from "rxjs";
|
||||
|
||||
import { Utils } from "@/jslib/common/src/misc/utils";
|
||||
|
||||
@Directive({
|
||||
selector: "[appAutofocus]",
|
||||
standalone: false,
|
||||
})
|
||||
export class AutofocusDirective {
|
||||
@Input() set appAutofocus(condition: boolean | string) {
|
||||
this.autofocus = condition === "" || condition === true;
|
||||
}
|
||||
|
||||
private autofocus: boolean;
|
||||
|
||||
constructor(
|
||||
private el: ElementRef,
|
||||
private ngZone: NgZone,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (!Utils.isMobileBrowser && this.autofocus) {
|
||||
if (this.ngZone.isStable) {
|
||||
this.el.nativeElement.focus();
|
||||
} else {
|
||||
this.ngZone.onStable.pipe(take(1)).subscribe(() => this.el.nativeElement.focus());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src-gui/angular/directives/blur-click.directive.ts
Normal file
13
src-gui/angular/directives/blur-click.directive.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Directive, ElementRef, HostListener } from "@angular/core";
|
||||
|
||||
@Directive({
|
||||
selector: "[appBlurClick]",
|
||||
standalone: false,
|
||||
})
|
||||
export class BlurClickDirective {
|
||||
constructor(private el: ElementRef) {}
|
||||
|
||||
@HostListener("click") onClick() {
|
||||
this.el.nativeElement.blur();
|
||||
}
|
||||
}
|
||||
60
src-gui/angular/directives/box-row.directive.ts
Normal file
60
src-gui/angular/directives/box-row.directive.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Directive, ElementRef, HostListener, OnInit } from "@angular/core";
|
||||
|
||||
@Directive({
|
||||
selector: "[appBoxRow]",
|
||||
standalone: false,
|
||||
})
|
||||
export class BoxRowDirective implements OnInit {
|
||||
el: HTMLElement = null;
|
||||
formEls: Element[];
|
||||
|
||||
constructor(elRef: ElementRef) {
|
||||
this.el = elRef.nativeElement;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.formEls = Array.from(
|
||||
this.el.querySelectorAll('input:not([type="hidden"]), select, textarea'),
|
||||
);
|
||||
this.formEls.forEach((formEl) => {
|
||||
formEl.addEventListener(
|
||||
"focus",
|
||||
() => {
|
||||
this.el.classList.add("active");
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
formEl.addEventListener(
|
||||
"blur",
|
||||
() => {
|
||||
this.el.classList.remove("active");
|
||||
},
|
||||
false,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@HostListener("click", ["$event"]) onClick(event: Event) {
|
||||
const target = event.target as HTMLElement;
|
||||
if (
|
||||
target !== this.el &&
|
||||
!target.classList.contains("progress") &&
|
||||
!target.classList.contains("progress-bar")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.formEls.length > 0) {
|
||||
const formEl = this.formEls[0] as HTMLElement;
|
||||
if (formEl.tagName.toLowerCase() === "input") {
|
||||
const inputEl = formEl as HTMLInputElement;
|
||||
if (inputEl.type != null && inputEl.type.toLowerCase() === "checkbox") {
|
||||
inputEl.click();
|
||||
return;
|
||||
}
|
||||
}
|
||||
formEl.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src-gui/angular/directives/fallback-src.directive.ts
Normal file
15
src-gui/angular/directives/fallback-src.directive.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Directive, ElementRef, HostListener, Input } from "@angular/core";
|
||||
|
||||
@Directive({
|
||||
selector: "[appFallbackSrc]",
|
||||
standalone: false,
|
||||
})
|
||||
export class FallbackSrcDirective {
|
||||
@Input("appFallbackSrc") appFallbackSrc: string;
|
||||
|
||||
constructor(private el: ElementRef) {}
|
||||
|
||||
@HostListener("error") onError() {
|
||||
this.el.nativeElement.src = this.appFallbackSrc;
|
||||
}
|
||||
}
|
||||
11
src-gui/angular/directives/stop-click.directive.ts
Normal file
11
src-gui/angular/directives/stop-click.directive.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Directive, HostListener } from "@angular/core";
|
||||
|
||||
@Directive({
|
||||
selector: "[appStopClick]",
|
||||
standalone: false,
|
||||
})
|
||||
export class StopClickDirective {
|
||||
@HostListener("click", ["$event"]) onClick($event: MouseEvent) {
|
||||
$event.preventDefault();
|
||||
}
|
||||
}
|
||||
11
src-gui/angular/directives/stop-prop.directive.ts
Normal file
11
src-gui/angular/directives/stop-prop.directive.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Directive, HostListener } from "@angular/core";
|
||||
|
||||
@Directive({
|
||||
selector: "[appStopProp]",
|
||||
standalone: false,
|
||||
})
|
||||
export class StopPropDirective {
|
||||
@HostListener("click", ["$event"]) onClick($event: MouseEvent) {
|
||||
$event.stopPropagation();
|
||||
}
|
||||
}
|
||||
15
src-gui/angular/pipes/i18n.pipe.ts
Normal file
15
src-gui/angular/pipes/i18n.pipe.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Pipe, PipeTransform } from "@angular/core";
|
||||
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
|
||||
@Pipe({
|
||||
name: "i18n",
|
||||
standalone: false,
|
||||
})
|
||||
export class I18nPipe implements PipeTransform {
|
||||
constructor(private i18nService: I18nService) {}
|
||||
|
||||
transform(id: string, p1?: string, p2?: string, p3?: string): string {
|
||||
return this.i18nService.t(id, p1, p2, p3);
|
||||
}
|
||||
}
|
||||
170
src-gui/angular/scss/bwicons/fonts/bwi-font.svg
Normal file
170
src-gui/angular/scss/bwicons/fonts/bwi-font.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 262 KiB |
BIN
src-gui/angular/scss/bwicons/fonts/bwi-font.ttf
Normal file
BIN
src-gui/angular/scss/bwicons/fonts/bwi-font.ttf
Normal file
Binary file not shown.
BIN
src-gui/angular/scss/bwicons/fonts/bwi-font.woff
Normal file
BIN
src-gui/angular/scss/bwicons/fonts/bwi-font.woff
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user