mirror of
https://github.com/bitwarden/directory-connector
synced 2026-03-01 02:31:09 +00:00
Compare commits
44 Commits
context-ru
...
restructur
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4f84fa209 | ||
|
|
651867a2e9 | ||
|
|
2f713578a6 | ||
|
|
3af1e31168 | ||
|
|
a74973ccfa | ||
|
|
4eb74cdeb4 | ||
|
|
b1a3859516 | ||
|
|
4c7afc0e64 | ||
|
|
4b079a3ec9 | ||
|
|
77873c3075 | ||
|
|
9997e988e6 | ||
|
|
abdddacb06 | ||
|
|
d5566c56b1 | ||
|
|
a019555143 | ||
|
|
b3cb369ed8 | ||
|
|
06edf4cf91 | ||
|
|
623382f9e1 | ||
|
|
1aad9e1cbe | ||
|
|
3059934d4c | ||
|
|
42cf13df08 | ||
|
|
1a9f0a2ca7 | ||
|
|
30b3595de3 | ||
|
|
a0e74948bd | ||
|
|
9f8018e8f8 | ||
|
|
28f0ff4b24 | ||
|
|
0bff38c459 | ||
|
|
14fc69c810 | ||
|
|
1ad0aea61f | ||
|
|
f41156969c | ||
|
|
39b151b1e0 | ||
|
|
483f26fa6f | ||
|
|
8849385d1b | ||
|
|
a7aff97360 | ||
|
|
94ff20f69f | ||
|
|
7381857296 | ||
|
|
ba17d5b438 | ||
|
|
b5d31e693b | ||
|
|
2854a2eba1 | ||
|
|
4485ecab3c | ||
|
|
9e3b2d2d95 | ||
|
|
b2997358dc | ||
|
|
db258f0191 | ||
|
|
19d7884933 | ||
|
|
21ce02f431 |
1187
.claude/CLAUDE.md
1187
.claude/CLAUDE.md
File diff suppressed because it is too large
Load Diff
@@ -1,30 +0,0 @@
|
|||||||
---
|
|
||||||
description: "Provides a brief explanation of the code attached, including key components, notable patterns, and a code walkthrough."
|
|
||||||
---
|
|
||||||
|
|
||||||
# Code Explainer
|
|
||||||
|
|
||||||
Provide a brief explanation of the code attached. I'm trying to better understand it.
|
|
||||||
|
|
||||||
## Key Components
|
|
||||||
|
|
||||||
- Main classes/functions and their roles
|
|
||||||
- Important dependencies
|
|
||||||
- Critical flows
|
|
||||||
|
|
||||||
## Notable Patterns
|
|
||||||
|
|
||||||
- Design patterns used
|
|
||||||
- Architecture decisions
|
|
||||||
- Important abstractions
|
|
||||||
|
|
||||||
## Code Walkthrough
|
|
||||||
|
|
||||||
- How it works
|
|
||||||
- Key decision points
|
|
||||||
- Important considerations
|
|
||||||
|
|
||||||
## Gotchas & Tips
|
|
||||||
|
|
||||||
- Edge cases to watch for
|
|
||||||
- Performance considerations
|
|
||||||
239
.claude/plan.md
Normal file
239
.claude/plan.md
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
# Phase 2 PR #1: Flatten Account Model - IMPLEMENTATION COMPLETE
|
||||||
|
|
||||||
|
## Status: ✅ COMPLETED
|
||||||
|
|
||||||
|
**Implementation Date:** February 13, 2026
|
||||||
|
**All tests passing:** 120/120 ✅
|
||||||
|
**TypeScript compilation:** Success ✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Successfully implemented Phase 2 PR #1: Flatten Account Model. The Account model has been simplified from 177 lines (51 + 126 inherited) to 51 lines, removing the BaseAccount inheritance and flattening nested structures into direct properties.
|
||||||
|
|
||||||
|
## Changes Implemented
|
||||||
|
|
||||||
|
### Files Modified (7 files)
|
||||||
|
|
||||||
|
1. **`jslib/common/src/enums/stateVersion.ts`**
|
||||||
|
- Added `StateVersion.Five` for the flattened Account structure
|
||||||
|
- Updated `StateVersion.Latest = Five`
|
||||||
|
|
||||||
|
2. **`src/models/account.ts`**
|
||||||
|
- Removed `extends BaseAccount` inheritance
|
||||||
|
- Removed `ClientKeys` class (redundant)
|
||||||
|
- Flattened 6 authentication fields to top level:
|
||||||
|
- `userId`, `entityId`, `apiKeyClientId`
|
||||||
|
- `accessToken`, `refreshToken`, `apiKeyClientSecret`
|
||||||
|
- Kept `DirectoryConfigurations` and `DirectorySettings` unchanged
|
||||||
|
- Added compatibility fields with FIXME comment for jslib infrastructure:
|
||||||
|
- `data?`, `keys?`, `profile?`, `settings?`, `tokens?` (optional, unused)
|
||||||
|
- Simplified constructor without Object.assign
|
||||||
|
|
||||||
|
3. **`src/services/stateMigration.service.ts`**
|
||||||
|
- Added `migrateStateFrom3To4()` placeholder migration
|
||||||
|
- Added `migrateStateFrom4To5()` to flatten nested → flat Account structure
|
||||||
|
- Updated `migrate()` method with new case statements for v3→v4 and v4→v5
|
||||||
|
- Updated `migrateStateFrom1To2()` to use flattened structure (removed `account.profile`, `account.clientKeys`)
|
||||||
|
|
||||||
|
4. **`src/services/auth.service.ts`**
|
||||||
|
- Removed imports: `AccountKeys`, `AccountProfile`, `AccountTokens`
|
||||||
|
- Simplified account creation from 26 lines to 10 lines (62% reduction)
|
||||||
|
- Direct property assignment instead of nested objects with spread operators
|
||||||
|
|
||||||
|
5. **`src/services/state.service.ts`**
|
||||||
|
- Changed `account.profile.userId` → `account.userId`
|
||||||
|
- Removed `account.settings` from `scaffoldNewAccountDiskStorage`
|
||||||
|
- Added `settings` back to `resetAccount` for base class compatibility (unused but required)
|
||||||
|
|
||||||
|
6. **`src/services/authService.spec.ts`**
|
||||||
|
- Removed imports: `AccountKeys`, `AccountProfile`, `AccountTokens`
|
||||||
|
- Updated test expectations to match new flat Account structure
|
||||||
|
|
||||||
|
### Files Created (1 file)
|
||||||
|
|
||||||
|
7. **`src/services/stateMigration.service.spec.ts`**
|
||||||
|
- Comprehensive migration test suite (5 tests, 210 lines)
|
||||||
|
- Tests flattening nested account structure
|
||||||
|
- Tests handling missing nested objects gracefully
|
||||||
|
- Tests empty account list
|
||||||
|
- Tests preservation of directory configurations and settings
|
||||||
|
- Tests state version update
|
||||||
|
|
||||||
|
## Code Reduction Achieved
|
||||||
|
|
||||||
|
- **Account model:** 177 lines (51 + 126 inherited) → 51 lines (71% reduction)
|
||||||
|
- **AuthService account creation:** 26 lines → 10 lines (62% reduction)
|
||||||
|
- **Import statements removed:** 5 jslib imports across multiple files
|
||||||
|
|
||||||
|
## Migration Logic
|
||||||
|
|
||||||
|
### State Version v4 → v5 Migration
|
||||||
|
|
||||||
|
The `migrateStateFrom4To5()` method handles conversion from nested to flat structure:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// OLD (nested structure):
|
||||||
|
{
|
||||||
|
profile: {
|
||||||
|
userId: "CLIENT_ID",
|
||||||
|
entityId: "CLIENT_ID",
|
||||||
|
apiKeyClientId: "organization.CLIENT_ID"
|
||||||
|
},
|
||||||
|
tokens: {
|
||||||
|
accessToken: "token",
|
||||||
|
refreshToken: "refresh"
|
||||||
|
},
|
||||||
|
keys: {
|
||||||
|
apiKeyClientSecret: "secret"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NEW (flat structure):
|
||||||
|
{
|
||||||
|
userId: "CLIENT_ID",
|
||||||
|
entityId: "CLIENT_ID",
|
||||||
|
apiKeyClientId: "organization.CLIENT_ID",
|
||||||
|
accessToken: "token",
|
||||||
|
refreshToken: "refresh",
|
||||||
|
apiKeyClientSecret: "secret"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Migration Safety:**
|
||||||
|
|
||||||
|
- Null-safe property access with `??` operator
|
||||||
|
- Preserves all directory configurations and settings
|
||||||
|
- Falls back to userId if profile.userId doesn't exist
|
||||||
|
- Handles empty account lists gracefully
|
||||||
|
|
||||||
|
## Test Results
|
||||||
|
|
||||||
|
### Unit Tests: ✅ PASS
|
||||||
|
|
||||||
|
```
|
||||||
|
Test Suites: 14 passed, 14 total
|
||||||
|
Tests: 120 passed, 120 total
|
||||||
|
```
|
||||||
|
|
||||||
|
New tests added:
|
||||||
|
|
||||||
|
- `should flatten nested account structure` ✅
|
||||||
|
- `should handle missing nested objects gracefully` ✅
|
||||||
|
- `should handle empty account list` ✅
|
||||||
|
- `should preserve directory configurations and settings` ✅
|
||||||
|
- `should update state version after successful migration` ✅
|
||||||
|
|
||||||
|
### TypeScript Compilation: ✅ PASS
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run test:types
|
||||||
|
```
|
||||||
|
|
||||||
|
All type checks pass with zero errors.
|
||||||
|
|
||||||
|
## Technical Notes
|
||||||
|
|
||||||
|
### Compatibility Fields
|
||||||
|
|
||||||
|
Added optional compatibility fields to Account model to satisfy jslib infrastructure type constraints:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// FIXME: Remove these compatibility fields after StateServiceVNext migration (PR #990) is merged
|
||||||
|
// These fields are unused but required for type compatibility with jslib's StateService infrastructure
|
||||||
|
data?: any;
|
||||||
|
keys?: any;
|
||||||
|
profile?: any;
|
||||||
|
settings?: any;
|
||||||
|
tokens?: any;
|
||||||
|
```
|
||||||
|
|
||||||
|
These will be removed after PR #990 (StateServiceVNext) merges and old StateService is deleted.
|
||||||
|
|
||||||
|
### Key Architectural Decision
|
||||||
|
|
||||||
|
Chose to add compatibility fields rather than refactor entire jslib infrastructure because:
|
||||||
|
|
||||||
|
1. PR #990 (StateServiceVNext) will eventually replace this infrastructure
|
||||||
|
2. Minimizes changes needed in this PR
|
||||||
|
3. Avoids conflicts with PR #990
|
||||||
|
4. Can be cleaned up later
|
||||||
|
|
||||||
|
## What This Enables
|
||||||
|
|
||||||
|
### Immediate Benefits
|
||||||
|
|
||||||
|
- ✅ Simplified Account model (71% code reduction)
|
||||||
|
- ✅ Clearer authentication field structure
|
||||||
|
- ✅ Easier debugging (no nested property access)
|
||||||
|
- ✅ Self-documenting code (obvious what DC needs)
|
||||||
|
|
||||||
|
### Enables Future Work
|
||||||
|
|
||||||
|
- **Phase 2 PR #2:** Remove StateFactory infrastructure
|
||||||
|
- **Phase 2 PR #3:** Delete ~90 unused jslib files including:
|
||||||
|
- EncString (only used by old nested Account)
|
||||||
|
- SymmetricCryptoKey (only used by old nested Account)
|
||||||
|
- OrganizationData (completely unused)
|
||||||
|
- ProviderData (completely unused)
|
||||||
|
- AccountKeys, AccountProfile, AccountTokens, AccountData, AccountSettings
|
||||||
|
|
||||||
|
## Merge Strategy
|
||||||
|
|
||||||
|
**Conflict Management:**
|
||||||
|
|
||||||
|
- This PR targets current codebase (with old StateService)
|
||||||
|
- Will conflict with PR #990 (StateServiceVNext) when it merges
|
||||||
|
- Plan: Rebase this PR after #990 merges
|
||||||
|
- Expected conflicts: StateService files, Account model structure
|
||||||
|
- Resolution: Keep StateServiceVNext changes, apply Account flattening to new structure
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Review & Test:** Thorough code review and manual testing
|
||||||
|
2. **Create PR:** Open PR with comprehensive description and test results
|
||||||
|
3. **Manual Testing Scenarios:**
|
||||||
|
- Fresh installation → authentication flow
|
||||||
|
- Existing installation → migration runs successfully
|
||||||
|
- All directory types → configuration persists correctly
|
||||||
|
- CLI authentication → flat structure works
|
||||||
|
4. **After Merge:**
|
||||||
|
- Begin Phase 2 PR #2: Remove StateFactory Infrastructure
|
||||||
|
- Monitor for any migration issues in production
|
||||||
|
|
||||||
|
## Related Work
|
||||||
|
|
||||||
|
- **Depends On:** None (can merge independently)
|
||||||
|
- **Blocks:** Phase 2 PR #2 (Remove StateFactory), Phase 2 PR #3 (Delete Unused jslib Files)
|
||||||
|
- **Conflicts With:** PR #990 (StateServiceVNext) - plan to rebase after #990 merges
|
||||||
|
- **Part Of:** Phase 2 tech debt cleanup (see CLAUDE.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Original Implementation Plan
|
||||||
|
|
||||||
|
[The original detailed step-by-step plan from the conversation has been preserved below for reference]
|
||||||
|
|
||||||
|
### Context
|
||||||
|
|
||||||
|
Directory Connector's Account model currently extends jslib's BaseAccount, inheriting 126 lines of complex nested structures designed for multi-account password manager features that DC doesn't use. This inheritance creates unnecessary coupling and blocks cleanup of unused jslib dependencies.
|
||||||
|
|
||||||
|
**Current State:**
|
||||||
|
|
||||||
|
- Account extends BaseAccount with nested objects: `profile.userId`, `tokens.accessToken`, `keys.apiKeyClientSecret`
|
||||||
|
- Only 6 fields from BaseAccount are actually used by DC
|
||||||
|
- 120+ lines of inherited code (AccountData, AccountKeys, AccountProfile, AccountSettings, AccountTokens) are unused
|
||||||
|
- Creates dependencies on EncString, SymmetricCryptoKey, OrganizationData, ProviderData that DC never uses
|
||||||
|
|
||||||
|
**Problem:**
|
||||||
|
|
||||||
|
- Unnecessary complexity for a single-account application
|
||||||
|
- Blocks deletion of unused jslib models (Phase 2 goal)
|
||||||
|
- Verbose account creation code (26 lines to set 6 fields)
|
||||||
|
- Difficult to understand what DC actually needs
|
||||||
|
|
||||||
|
**Goal:**
|
||||||
|
Flatten Account model to contain only the 8 fields DC uses, removing BaseAccount inheritance. This enables Phase 2 PR #2 and PR #3 to delete ~90 unused jslib files.
|
||||||
|
|
||||||
|
[Rest of original plan preserved in conversation transcript]
|
||||||
130
.claude/skills/commonjs-to-esm/skill.md
Normal file
130
.claude/skills/commonjs-to-esm/skill.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
---
|
||||||
|
userInvocable: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# CommonJS to ESM Conversion
|
||||||
|
|
||||||
|
Convert a file (or files) from CommonJS module syntax to ECMAScript Modules (ESM).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/commonjs-to-esm <file-path> [additional-file-paths...]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- `file-path` - Path to the file(s) to convert from CommonJS to ESM
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
/commonjs-to-esm src/services/auth.service.ts
|
||||||
|
/commonjs-to-esm src/utils/helper.ts src/utils/parser.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Process
|
||||||
|
|
||||||
|
This skill performs a comprehensive analysis and planning process:
|
||||||
|
|
||||||
|
### 1. Analyze Target File(s)
|
||||||
|
|
||||||
|
For each file to convert:
|
||||||
|
|
||||||
|
- Read the file contents
|
||||||
|
- Identify its purpose and functionality
|
||||||
|
- Catalog all CommonJS patterns used:
|
||||||
|
- `require()` statements
|
||||||
|
- `module.exports` assignments
|
||||||
|
- `exports.x = ...` assignments
|
||||||
|
- Dynamic requires
|
||||||
|
- `__dirname` and `__filename` usage
|
||||||
|
|
||||||
|
### 2. Find Dependents
|
||||||
|
|
||||||
|
- Search for all files that import/require the target file(s)
|
||||||
|
- Identify the import patterns used by dependents
|
||||||
|
- Map the dependency tree to understand impact scope
|
||||||
|
|
||||||
|
### 3. Analyze Dependencies
|
||||||
|
|
||||||
|
- List all modules the target file(s) depend on
|
||||||
|
- Determine if dependencies support ESM
|
||||||
|
- Identify potential blocking dependencies (CommonJS-only packages)
|
||||||
|
- Check for dynamic imports that may need special handling
|
||||||
|
|
||||||
|
### 4. Identify Conversion Challenges
|
||||||
|
|
||||||
|
Common issues to flag:
|
||||||
|
|
||||||
|
- `__dirname` and `__filename` (need `import.meta.url` conversion)
|
||||||
|
- Dynamic `require()` calls (need `import()` conversion)
|
||||||
|
- Conditional requires (need refactoring)
|
||||||
|
- JSON imports (need `assert { type: 'json' }`)
|
||||||
|
- CommonJS-only dependencies (may block conversion)
|
||||||
|
- Circular dependencies (may need restructuring)
|
||||||
|
|
||||||
|
### 5. Generate Conversion Plan
|
||||||
|
|
||||||
|
Create a step-by-step plan that includes:
|
||||||
|
|
||||||
|
**Target File Changes:**
|
||||||
|
|
||||||
|
- Convert `require()` to `import` statements
|
||||||
|
- Convert `module.exports` to `export` statements
|
||||||
|
- Update `__dirname`/`__filename` to use `import.meta.url`
|
||||||
|
- Handle dynamic imports appropriately
|
||||||
|
- Update file extensions if needed (e.g., `.js` to `.mjs`)
|
||||||
|
|
||||||
|
**Dependent File Changes:**
|
||||||
|
|
||||||
|
- Update all import statements in dependent files
|
||||||
|
- Ensure consistent naming (default vs named exports)
|
||||||
|
- Update path references if extensions change
|
||||||
|
|
||||||
|
**Configuration Changes:**
|
||||||
|
|
||||||
|
- `package.json`: Add `"type": "module"` or use `.mjs` extension
|
||||||
|
- `tsconfig.json`: Update `module` and `moduleResolution` settings
|
||||||
|
- Build tools: Update bundler/compiler configurations
|
||||||
|
|
||||||
|
**Testing Strategy:**
|
||||||
|
|
||||||
|
- Run unit tests after conversion
|
||||||
|
- Verify no runtime errors from import changes
|
||||||
|
- Check that all exports are accessible
|
||||||
|
- Test dynamic import scenarios
|
||||||
|
|
||||||
|
### 6. Risk Assessment
|
||||||
|
|
||||||
|
Evaluate:
|
||||||
|
|
||||||
|
- Number of files affected
|
||||||
|
- Complexity of CommonJS patterns used
|
||||||
|
- Presence of blocking dependencies
|
||||||
|
- Potential for breaking changes
|
||||||
|
|
||||||
|
### 7. Present Plan
|
||||||
|
|
||||||
|
Output a structured plan with:
|
||||||
|
|
||||||
|
- Summary of changes needed
|
||||||
|
- Ordered steps for execution
|
||||||
|
- List of files to modify
|
||||||
|
- Configuration changes required
|
||||||
|
- Testing checkpoints
|
||||||
|
- Risk factors and mitigation strategies
|
||||||
|
- Estimated scope (small/medium/large change)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- ESM is **not** compatible with CommonJS in all cases - ESM can import CommonJS, but CommonJS **cannot** require ESM
|
||||||
|
- This means conversions should generally proceed from leaf dependencies upward
|
||||||
|
- Some packages remain CommonJS-only and may block full conversion
|
||||||
|
- The skill generates a plan but does NOT automatically execute the conversion - review and approve first
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [Pure ESM package guide](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c)
|
||||||
|
- [Node.js ESM documentation](https://nodejs.org/api/esm.html)
|
||||||
|
- [TypeScript ESM support](https://www.typescriptlang.org/docs/handbook/esm-node.html)
|
||||||
23
.github/PULL_REQUEST_TEMPLATE.md
vendored
23
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -9,26 +9,3 @@
|
|||||||
## 📸 Screenshots
|
## 📸 Screenshots
|
||||||
|
|
||||||
<!-- Required for any UI changes; delete if not applicable. Use fixed width images for better display. -->
|
<!-- Required for any UI changes; delete if not applicable. Use fixed width images for better display. -->
|
||||||
|
|
||||||
## ⏰ Reminders before review
|
|
||||||
|
|
||||||
- Contributor guidelines followed
|
|
||||||
- All formatters and local linters executed and passed
|
|
||||||
- Written new unit and / or integration tests where applicable
|
|
||||||
- Used internationalization (i18n) for all UI strings
|
|
||||||
- CI builds passed
|
|
||||||
- Communicated to DevOps any deployment requirements
|
|
||||||
- Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team
|
|
||||||
|
|
||||||
## 🦮 Reviewer guidelines
|
|
||||||
|
|
||||||
<!-- Suggested interactions but feel free to use (or not) as you desire! -->
|
|
||||||
|
|
||||||
- 👍 (`:+1:`) or similar for great changes
|
|
||||||
- 📝 (`:memo:`) or ℹ️ (`:information_source:`) for notes or general info
|
|
||||||
- ❓ (`:question:`) for questions
|
|
||||||
- 🤔 (`:thinking:`) or 💭 (`:thought_balloon:`) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
|
|
||||||
- 🎨 (`:art:`) for suggestions / improvements
|
|
||||||
- ❌ (`:x:`) or ⚠️ (`:warning:`) for more significant problems or concerns needing attention
|
|
||||||
- 🌱 (`:seedling:`) or ♻️ (`:recycle:`) for future improvements or indications of technical debt
|
|
||||||
- ⛏ (`:pick:`) for minor or nitpick changes
|
|
||||||
|
|||||||
26
.github/workflows/build.yml
vendored
26
.github/workflows/build.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
node_version: ${{ steps.retrieve-node-version.outputs.node_version }}
|
node_version: ${{ steps.retrieve-node-version.outputs.node_version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
@@ -51,12 +51,12 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: '**/package-lock.json'
|
cache-dependency-path: '**/package-lock.json'
|
||||||
@@ -129,12 +129,12 @@ jobs:
|
|||||||
_NODE_VERSION: ${{ needs.setup.outputs.node_version }}
|
_NODE_VERSION: ${{ needs.setup.outputs.node_version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: '**/package-lock.json'
|
cache-dependency-path: '**/package-lock.json'
|
||||||
@@ -200,7 +200,7 @@ jobs:
|
|||||||
_NODE_VERSION: ${{ needs.setup.outputs.node_version }}
|
_NODE_VERSION: ${{ needs.setup.outputs.node_version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
@@ -209,7 +209,7 @@ jobs:
|
|||||||
choco install checksum --no-progress
|
choco install checksum --no-progress
|
||||||
|
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: '**/package-lock.json'
|
cache-dependency-path: '**/package-lock.json'
|
||||||
@@ -279,12 +279,12 @@ jobs:
|
|||||||
HUSKY: 0
|
HUSKY: 0
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: '**/package-lock.json'
|
cache-dependency-path: '**/package-lock.json'
|
||||||
@@ -379,12 +379,12 @@ jobs:
|
|||||||
HUSKY: 0
|
HUSKY: 0
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: '**/package-lock.json'
|
cache-dependency-path: '**/package-lock.json'
|
||||||
@@ -439,12 +439,12 @@ jobs:
|
|||||||
HUSKY: 0
|
HUSKY: 0
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: '**/package-lock.json'
|
cache-dependency-path: '**/package-lock.json'
|
||||||
|
|||||||
31
.github/workflows/integration-test.yml
vendored
31
.github/workflows/integration-test.yml
vendored
@@ -14,9 +14,9 @@ on:
|
|||||||
- "docker-compose.yml" # any change to Docker configuration
|
- "docker-compose.yml" # any change to Docker configuration
|
||||||
- "package.json" # dependencies
|
- "package.json" # dependencies
|
||||||
- "utils/**" # any change to test fixtures
|
- "utils/**" # any change to test fixtures
|
||||||
- "src/services/sync.service.ts" # core sync service used by all directory services
|
- "libs/services/sync.service.ts" # core sync service used by all directory services
|
||||||
- "src/services/directory-services/ldap-directory.service*" # LDAP directory service
|
- "libs/services/directory-services/ldap-directory.service*" # LDAP directory service
|
||||||
- "src/services/directory-services/gsuite-directory.service*" # Google Workspace directory service
|
- "libs/services/directory-services/gsuite-directory.service*" # Google Workspace directory service
|
||||||
# Add directory services here as we add test coverage
|
# Add directory services here as we add test coverage
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
@@ -24,9 +24,9 @@ on:
|
|||||||
- "docker-compose.yml" # any change to Docker configuration
|
- "docker-compose.yml" # any change to Docker configuration
|
||||||
- "package.json" # dependencies
|
- "package.json" # dependencies
|
||||||
- "utils/**" # any change to test fixtures
|
- "utils/**" # any change to test fixtures
|
||||||
- "src/services/sync.service.ts" # core sync service used by all directory services
|
- "libs/services/sync.service.ts" # core sync service used by all directory services
|
||||||
- "src/services/directory-services/ldap-directory.service*" # LDAP directory service
|
- "libs/services/directory-services/ldap-directory.service*" # LDAP directory service
|
||||||
- "src/services/directory-services/gsuite-directory.service*" # Google Workspace directory service
|
- "libs/services/directory-services/gsuite-directory.service*" # Google Workspace directory service
|
||||||
# Add directory services here as we add test coverage
|
# Add directory services here as we add test coverage
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repo
|
- name: Check out repo
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT"
|
echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: '**/package-lock.json'
|
cache-dependency-path: '**/package-lock.json'
|
||||||
@@ -94,12 +94,12 @@ jobs:
|
|||||||
- '.github/workflows/integration-test.yml'
|
- '.github/workflows/integration-test.yml'
|
||||||
- 'utils/**'
|
- 'utils/**'
|
||||||
- 'package.json'
|
- 'package.json'
|
||||||
- 'src/services/sync.service.ts'
|
- 'libs/services/sync.service.ts'
|
||||||
ldap:
|
ldap:
|
||||||
- 'docker-compose.yml'
|
- 'docker-compose.yml'
|
||||||
- 'src/services/directory-services/ldap-directory.service*'
|
- 'libs/services/directory-services/ldap-directory.service*'
|
||||||
google:
|
google:
|
||||||
- 'src/services/directory-services/gsuite-directory.service*'
|
- 'libs/services/directory-services/gsuite-directory.service*'
|
||||||
|
|
||||||
# LDAP
|
# LDAP
|
||||||
- name: Setup LDAP integration tests
|
- name: Setup LDAP integration tests
|
||||||
@@ -108,6 +108,9 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get -y install mkcert
|
sudo apt-get -y install mkcert
|
||||||
npm run test:integration:setup
|
npm run test:integration:setup
|
||||||
|
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
|
- name: Run LDAP integration tests
|
||||||
if: steps.changed-files.outputs.common == 'true' || steps.changed-files.outputs.ldap == 'true'
|
if: steps.changed-files.outputs.common == 'true' || steps.changed-files.outputs.ldap == 'true'
|
||||||
@@ -129,7 +132,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Report test results
|
- name: Report test results
|
||||||
id: report
|
id: report
|
||||||
uses: dorny/test-reporter@fe45e9537387dac839af0d33ba56eed8e24189e8 # v2.3.0
|
uses: dorny/test-reporter@b082adf0eced0765477756c2a610396589b8c637 # v2.5.0
|
||||||
# This will skip the job if it's a pull request from a fork, because that won't have permission to upload test results.
|
# This will skip the job if it's a pull request from a fork, because that won't have permission to upload test results.
|
||||||
# PRs from the repository and all other events are OK.
|
# PRs from the repository and all other events are OK.
|
||||||
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository) && !cancelled()
|
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository) && !cancelled()
|
||||||
@@ -143,4 +146,6 @@ jobs:
|
|||||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||||
|
|
||||||
- name: Upload results to codecov.io
|
- name: Upload results to codecov.io
|
||||||
uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1
|
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||||
|
with:
|
||||||
|
report_type: test_results
|
||||||
|
|||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
|||||||
release_version: ${{ steps.version.outputs.version }}
|
release_version: ${{ steps.version.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
|
|||||||
10
.github/workflows/test.yml
vendored
10
.github/workflows/test.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repo
|
- name: Check out repo
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT"
|
echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: '**/package-lock.json'
|
cache-dependency-path: '**/package-lock.json'
|
||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
run: npm run test --coverage
|
run: npm run test --coverage
|
||||||
|
|
||||||
- name: Report test results
|
- name: Report test results
|
||||||
uses: dorny/test-reporter@fe45e9537387dac839af0d33ba56eed8e24189e8 # v2.3.0
|
uses: dorny/test-reporter@b082adf0eced0765477756c2a610396589b8c637 # v2.5.0
|
||||||
# This will skip the job if it's a pull request from a fork, because that won't have permission to upload test results.
|
# This will skip the job if it's a pull request from a fork, because that won't have permission to upload test results.
|
||||||
# PRs from the repository and all other events are OK.
|
# PRs from the repository and all other events are OK.
|
||||||
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository) && !cancelled()
|
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository) && !cancelled()
|
||||||
@@ -67,4 +67,6 @@ jobs:
|
|||||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||||
|
|
||||||
- name: Upload results to codecov.io
|
- name: Upload results to codecov.io
|
||||||
uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1
|
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||||
|
with:
|
||||||
|
report_type: test_results
|
||||||
|
|||||||
2
.github/workflows/version-bump.yml
vendored
2
.github/workflows/version-bump.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
|||||||
permission-contents: write
|
permission-contents: write
|
||||||
|
|
||||||
- name: Checkout Branch
|
- name: Checkout Branch
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.app-token.outputs.token }}
|
token: ${{ steps.app-token.outputs.token }}
|
||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
|
|||||||
@@ -1,156 +0,0 @@
|
|||||||
# ESM Migration Plan
|
|
||||||
|
|
||||||
## Migration Status: Partial Success
|
|
||||||
|
|
||||||
The ESM migration has been **partially completed**. The source code is now ESM-compatible with `"type": "module"` in package.json, and webpack outputs CommonJS bundles (`.cjs`) for Node.js compatibility.
|
|
||||||
|
|
||||||
### What Works
|
|
||||||
|
|
||||||
- ✅ CLI build (`bwdc.cjs`) - builds and runs successfully
|
|
||||||
- ✅ Electron main process (`main.cjs`) - builds successfully
|
|
||||||
- ✅ All 130 tests pass
|
|
||||||
- ✅ Source code uses ESM syntax (import/export)
|
|
||||||
|
|
||||||
### What Doesn't Work
|
|
||||||
|
|
||||||
- ❌ Electron renderer build - **pre-existing type errors in jslib** (not caused by this migration)
|
|
||||||
|
|
||||||
The renderer build was failing with 37 TypeScript errors in `jslib/` **before** the ESM migration began. These are ArrayBuffer/SharedArrayBuffer type compatibility issues in the jslib submodule that need to be addressed separately.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Changes Made
|
|
||||||
|
|
||||||
### 1. package.json
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "module",
|
|
||||||
"main": "main.cjs"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. tsconfig.json
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"module": "ES2020",
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"noEmitOnError": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Webpack Configurations
|
|
||||||
|
|
||||||
**CLI (webpack.cli.cjs)**
|
|
||||||
|
|
||||||
- Output changed to `.cjs` extension
|
|
||||||
- Added `transpileOnly: true` to ts-loader for faster builds
|
|
||||||
|
|
||||||
**Main (webpack.main.cjs)**
|
|
||||||
|
|
||||||
- Output changed to `.cjs` extension
|
|
||||||
- Added `transpileOnly: true` to ts-loader
|
|
||||||
|
|
||||||
**Renderer (webpack.renderer.cjs)**
|
|
||||||
|
|
||||||
- Created separate `tsconfig.renderer.json` to isolate Angular compilation
|
|
||||||
- Removed ESM output experiments (not compatible with Angular's webpack plugin)
|
|
||||||
|
|
||||||
### 4. src-cli/package.json
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "module",
|
|
||||||
"bin": {
|
|
||||||
"bwdc": "../build-cli/bwdc.cjs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. New File: tsconfig.renderer.json
|
|
||||||
|
|
||||||
Dedicated TypeScript config for Angular renderer to isolate from jslib type issues.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Architecture Decision
|
|
||||||
|
|
||||||
### Why CJS Output Instead of ESM Output?
|
|
||||||
|
|
||||||
The migration uses a **hybrid approach**:
|
|
||||||
|
|
||||||
- **Source code**: ESM syntax (`import`/`export`)
|
|
||||||
- **Build output**: CommonJS (`.cjs` files)
|
|
||||||
|
|
||||||
This approach was chosen because:
|
|
||||||
|
|
||||||
1. **lowdb v1 incompatibility**: The legacy lowdb v1 used in jslib doesn't work properly with ESM output due to lodash interop issues
|
|
||||||
|
|
||||||
2. **Native module compatibility**: keytar and other native modules work better with CJS
|
|
||||||
|
|
||||||
3. **Electron compatibility**: Electron's main process ESM support is still maturing
|
|
||||||
|
|
||||||
4. **jslib constraints**: The jslib submodule is read-only and contains CJS-only patterns
|
|
||||||
|
|
||||||
The webpack bundler transpiles ESM source to CJS output, giving us modern syntax in the codebase while maintaining runtime compatibility.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Blocking Issues for Full ESM
|
|
||||||
|
|
||||||
### 1. jslib Submodule (Read-Only)
|
|
||||||
|
|
||||||
The jslib folder contains:
|
|
||||||
|
|
||||||
- `lowdb` v1.0.0 usage (CJS-only, v7 is ESM but has breaking API changes)
|
|
||||||
- `node-fetch` v2.7.0 usage (CJS-only, v3 is ESM-only)
|
|
||||||
- Pre-existing TypeScript errors (ArrayBuffer type mismatches)
|
|
||||||
|
|
||||||
### 2. Angular Webpack Plugin
|
|
||||||
|
|
||||||
The `@ngtools/webpack` plugin does its own TypeScript compilation and doesn't support `transpileOnly` mode, so it surfaces type errors from jslib.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Future Work
|
|
||||||
|
|
||||||
To complete full ESM migration:
|
|
||||||
|
|
||||||
1. **Update jslib submodule** - Fix type errors, upgrade to ESM-compatible dependencies
|
|
||||||
2. **Upgrade lowdb** - From v1 to v7 (requires rewriting storage layer)
|
|
||||||
3. **Remove node-fetch** - Use native `fetch` (Node 18+) or upgrade to v3
|
|
||||||
4. **Enable ESM output** - Once dependencies are updated, change webpack output to ESM
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing the Migration
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build CLI
|
|
||||||
npm run build:cli
|
|
||||||
node ./build-cli/bwdc.cjs --help
|
|
||||||
|
|
||||||
# Build Electron main
|
|
||||||
npm run build:main
|
|
||||||
|
|
||||||
# Run tests
|
|
||||||
npm test
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Files Changed
|
|
||||||
|
|
||||||
| File | Change |
|
|
||||||
| ------------------------ | ---------------------------------------------------- |
|
|
||||||
| `package.json` | Added `"type": "module"`, changed main to `main.cjs` |
|
|
||||||
| `tsconfig.json` | Added `skipLibCheck`, `noEmitOnError` |
|
|
||||||
| `tsconfig.renderer.json` | New file for Angular compilation |
|
|
||||||
| `webpack.cli.cjs` | Output to `.cjs`, added `transpileOnly` |
|
|
||||||
| `webpack.main.cjs` | Output to `.cjs`, added `transpileOnly` |
|
|
||||||
| `webpack.renderer.cjs` | Use separate tsconfig |
|
|
||||||
| `src-cli/package.json` | Added `"type": "module"`, updated bin path |
|
|
||||||
21
angular.json
21
angular.json
@@ -14,19 +14,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": ".",
|
"root": ".",
|
||||||
"sourceRoot": "src",
|
"sourceRoot": "src-gui",
|
||||||
"prefix": "app",
|
"prefix": "app",
|
||||||
"architect": {
|
"architect": {
|
||||||
"build": {
|
"build": {
|
||||||
"builder": "@angular-devkit/build-angular:browser",
|
"builder": "@angular/build:application",
|
||||||
"options": {
|
"options": {
|
||||||
"outputPath": "dist",
|
"outputPath": {
|
||||||
"index": "src/index.html",
|
"base": "dist"
|
||||||
"main": "src/main.ts",
|
},
|
||||||
|
"index": "src-gui/index.html",
|
||||||
"tsConfig": "tsconfig.json",
|
"tsConfig": "tsconfig.json",
|
||||||
"assets": [],
|
"assets": [
|
||||||
"styles": [],
|
{ "glob": "**/*", "input": "src-gui/images", "output": "images" },
|
||||||
"scripts": []
|
{ "glob": "**/*", "input": "src-gui/locales", "output": "locales" }
|
||||||
|
],
|
||||||
|
"styles": ["src-gui/scss/styles.scss"],
|
||||||
|
"scripts": [],
|
||||||
|
"browser": "src-gui/app/main.ts"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,3 +16,22 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "1389:1389"
|
- "1389:1389"
|
||||||
- "1636:1636"
|
- "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
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"output": "dist",
|
"output": "dist",
|
||||||
"app": "build"
|
"app": "build"
|
||||||
},
|
},
|
||||||
"afterSign": "scripts/notarize.js",
|
"afterSign": "scripts/notarize.mjs",
|
||||||
"mac": {
|
"mac": {
|
||||||
"artifactName": "Bitwarden-Connector-${version}-mac.${ext}",
|
"artifactName": "Bitwarden-Connector-${version}-mac.${ext}",
|
||||||
"category": "public.app-category.productivity",
|
"category": "public.app-category.productivity",
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
},
|
},
|
||||||
"win": {
|
"win": {
|
||||||
"target": ["portable", "nsis"],
|
"target": ["portable", "nsis"],
|
||||||
"sign": "scripts/sign.js"
|
"sign": "scripts/sign.mjs"
|
||||||
},
|
},
|
||||||
"linux": {
|
"linux": {
|
||||||
"category": "Utility",
|
"category": "Utility",
|
||||||
|
|||||||
@@ -87,14 +87,24 @@ export default [
|
|||||||
"newlines-between": "always",
|
"newlines-between": "always",
|
||||||
pathGroups: [
|
pathGroups: [
|
||||||
{
|
{
|
||||||
pattern: "@/jslib/**/*",
|
pattern: "@/libs/**",
|
||||||
group: "external",
|
group: "external",
|
||||||
position: "after",
|
position: "after",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: "@/src/**/*",
|
pattern: "@/jslib/**",
|
||||||
group: "parent",
|
group: "external",
|
||||||
position: "before",
|
position: "after",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "@/src-gui/**",
|
||||||
|
group: "external",
|
||||||
|
position: "after",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "@/src-cli/**",
|
||||||
|
group: "external",
|
||||||
|
position: "after",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
pathGroupsExcludedImportTypes: ["builtin"],
|
pathGroupsExcludedImportTypes: ["builtin"],
|
||||||
|
|||||||
0
src/global.d.ts → global.d.ts
vendored
0
src/global.d.ts → global.d.ts
vendored
@@ -1,14 +1,14 @@
|
|||||||
const { pathsToModuleNameMapper } = require("ts-jest");
|
import { pathsToModuleNameMapper } from "ts-jest";
|
||||||
const { compilerOptions } = require("./tsconfig");
|
import tsconfig from "./tsconfig.json" with { type: "json" };
|
||||||
|
|
||||||
const tsPreset = require("ts-jest/jest-preset");
|
import angularPresetsModule from "jest-preset-angular/presets/index.js";
|
||||||
const angularPreset = require("jest-preset-angular/jest-preset");
|
|
||||||
const { defaultTransformerOptions } = require("jest-preset-angular/presets");
|
const { defaultTransformerOptions } = angularPresetsModule;
|
||||||
|
|
||||||
|
const { compilerOptions } = tsconfig;
|
||||||
|
|
||||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||||
module.exports = {
|
export default {
|
||||||
// ...tsPreset,
|
|
||||||
// ...angularPreset,
|
|
||||||
preset: "jest-preset-angular",
|
preset: "jest-preset-angular",
|
||||||
|
|
||||||
reporters: ["default", "jest-junit"],
|
reporters: ["default", "jest-junit"],
|
||||||
@@ -24,20 +24,13 @@ module.exports = {
|
|||||||
|
|
||||||
roots: ["<rootDir>"],
|
roots: ["<rootDir>"],
|
||||||
modulePaths: [compilerOptions.baseUrl],
|
modulePaths: [compilerOptions.baseUrl],
|
||||||
moduleNameMapper: {
|
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: "<rootDir>/" }),
|
||||||
...pathsToModuleNameMapper(compilerOptions.paths, { prefix: "<rootDir>/" }),
|
|
||||||
// ESM compatibility: mock import.meta.url for tests
|
|
||||||
"^(\\.{1,2}/.*)\\.js$": "$1",
|
|
||||||
},
|
|
||||||
setupFilesAfterEnv: ["<rootDir>/test.setup.ts"],
|
setupFilesAfterEnv: ["<rootDir>/test.setup.ts"],
|
||||||
// Workaround for a memory leak that crashes tests in CI:
|
// Workaround for a memory leak that crashes tests in CI:
|
||||||
// https://github.com/facebook/jest/issues/9430#issuecomment-1149882002
|
// https://github.com/facebook/jest/issues/9430#issuecomment-1149882002
|
||||||
// Also anecdotally improves performance when run locally
|
// Also anecdotally improves performance when run locally
|
||||||
maxWorkers: 3,
|
maxWorkers: 3,
|
||||||
|
|
||||||
// ESM support
|
|
||||||
extensionsToTreatAsEsm: [".ts"],
|
|
||||||
|
|
||||||
transform: {
|
transform: {
|
||||||
"^.+\\.tsx?$": [
|
"^.+\\.tsx?$": [
|
||||||
"jest-preset-angular",
|
"jest-preset-angular",
|
||||||
@@ -50,8 +43,6 @@ module.exports = {
|
|||||||
// Makes tests run faster and reduces size/rate of leak, but loses typechecking on test code
|
// Makes tests run faster and reduces size/rate of leak, but loses typechecking on test code
|
||||||
// See https://bitwarden.atlassian.net/browse/EC-497 for more info
|
// See https://bitwarden.atlassian.net/browse/EC-497 for more info
|
||||||
isolatedModules: true,
|
isolatedModules: true,
|
||||||
// ESM support
|
|
||||||
useESM: true,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
745
jslib-removal-plan.md
Normal file
745
jslib-removal-plan.md
Normal file
@@ -0,0 +1,745 @@
|
|||||||
|
# Plan: Remove StateService and jslib Dependencies
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Directory Connector currently depends on StateService from jslib, which is a massive pre-StateProvider monolith containing 200+ getter/setter methods for all Bitwarden clients. This creates significant maintenance burden and blocks deletion of unused jslib code.
|
||||||
|
|
||||||
|
**Current State (Phase 1 Complete):**
|
||||||
|
|
||||||
|
- ✅ StateServiceVNext has been implemented with a flat key-value structure
|
||||||
|
- ✅ Migration service handles transition from old account-based structure to new flat structure
|
||||||
|
- ⚠️ Both old and new StateService implementations coexist during migration
|
||||||
|
- ❌ Three jslib services still depend on old StateService: TokenService, CryptoService, EnvironmentService
|
||||||
|
- ❌ Two Electron components depend on old StateService: WindowMain, TrayMain
|
||||||
|
|
||||||
|
**Problem:**
|
||||||
|
The old StateService cannot be removed until all dependencies are eliminated. Analysis reveals:
|
||||||
|
|
||||||
|
- **TokenService**: Used for API authentication (9/32 methods actually used)
|
||||||
|
- **CryptoService**: Completely unused by DC (0/61 methods used) - carried over from monolith
|
||||||
|
- **EnvironmentService**: Used for custom server URLs (4/11 methods used)
|
||||||
|
- **WindowMain/TrayMain**: Used for Electron window/tray state persistence (6 methods total)
|
||||||
|
|
||||||
|
**Goal:**
|
||||||
|
Replace jslib services with simplified DC-specific implementations that use StateServiceVNext, enabling complete removal of old StateService and unlocking Phase 2 (jslib code cleanup).
|
||||||
|
|
||||||
|
**User Decisions:**
|
||||||
|
|
||||||
|
1. ✅ Create simplified DC-specific versions of Token/Environment services (clean break from jslib)
|
||||||
|
2. ✅ Keep WindowMain/TrayMain as-is (minimize scope, focus on StateService removal)
|
||||||
|
3. ✅ Automatic migration on first launch (transparent to users)
|
||||||
|
|
||||||
|
## Critical Files
|
||||||
|
|
||||||
|
### Files to Create (New Implementations)
|
||||||
|
|
||||||
|
- `src/services/token/token.service.ts` - DC-specific token service
|
||||||
|
- `src/abstractions/token.service.ts` - Token service interface
|
||||||
|
- `src/services/environment/environment.service.ts` - DC-specific environment service
|
||||||
|
- `src/abstractions/environment.service.ts` - Environment service interface
|
||||||
|
- `src/utils/jwt.util.ts` - JWT decoding utility (no dependencies)
|
||||||
|
|
||||||
|
### Files to Modify (Update Dependencies)
|
||||||
|
|
||||||
|
- `src/services/api.service.ts` - Switch from jslib TokenService to DC TokenService
|
||||||
|
- `src/services/auth.service.ts` - Update EnvironmentService import
|
||||||
|
- `src/services/sync.service.ts` - Update EnvironmentService import
|
||||||
|
- `src/commands/config.command.ts` - Update EnvironmentService import
|
||||||
|
- `src/bwdc.ts` - Remove old StateService, instantiate new services
|
||||||
|
- `src/main.ts` - Remove old StateService, instantiate new services
|
||||||
|
- `src/app/services/services.module.ts` - Remove old StateService, provide new services
|
||||||
|
- `src/app/app.component.ts` - Update TokenService import
|
||||||
|
- `jslib/electron/src/window.main.ts` - Adapt to use StateServiceVNext
|
||||||
|
- `jslib/electron/src/tray.main.ts` - Adapt to use StateServiceVNext
|
||||||
|
|
||||||
|
### Files to Delete (After Migration)
|
||||||
|
|
||||||
|
- `jslib/common/src/services/token.service.ts` - jslib TokenService
|
||||||
|
- `jslib/common/src/abstractions/token.service.ts` - jslib TokenService interface
|
||||||
|
- `jslib/common/src/services/crypto.service.ts` - Unused CryptoService
|
||||||
|
- `jslib/common/src/abstractions/crypto.service.ts` - Unused CryptoService interface
|
||||||
|
- `jslib/common/src/services/environment.service.ts` - jslib EnvironmentService
|
||||||
|
- `jslib/common/src/abstractions/environment.service.ts` - jslib EnvironmentService interface
|
||||||
|
- `src/services/state-service/state.service.ts` - Old DC StateService
|
||||||
|
- `src/abstractions/state.service.ts` - Old DC StateService interface
|
||||||
|
- `jslib/common/src/services/state.service.ts` - Old jslib StateService
|
||||||
|
- `jslib/common/src/abstractions/state.service.ts` - Old jslib StateService interface
|
||||||
|
|
||||||
|
## Implementation Plan
|
||||||
|
|
||||||
|
### Step 1: Create JWT Utility (No Dependencies)
|
||||||
|
|
||||||
|
Create `src/utils/jwt.util.ts` with standalone JWT decoding function:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface DecodedToken {
|
||||||
|
exp: number;
|
||||||
|
iat: number;
|
||||||
|
nbf: number;
|
||||||
|
sub: string; // user ID
|
||||||
|
client_id?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeJwt(token: string): DecodedToken {
|
||||||
|
// Validate JWT structure (3 parts: header.payload.signature)
|
||||||
|
const parts = token.split(".");
|
||||||
|
if (parts.length !== 3) {
|
||||||
|
throw new Error("Invalid JWT format");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode payload (base64url to JSON)
|
||||||
|
const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
||||||
|
|
||||||
|
return JSON.parse(atob(payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTokenExpirationDate(token: string): Date | null {
|
||||||
|
const decoded = decodeJwt(token);
|
||||||
|
if (!decoded.exp) return null;
|
||||||
|
return new Date(decoded.exp * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tokenSecondsRemaining(token: string, offsetSeconds = 0): number {
|
||||||
|
const expDate = getTokenExpirationDate(token);
|
||||||
|
if (!expDate) return 0;
|
||||||
|
|
||||||
|
const msRemaining = expDate.getTime() - Date.now() - offsetSeconds * 1000;
|
||||||
|
return Math.floor(msRemaining / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tokenNeedsRefresh(token: string, minutesBeforeExpiration = 5): boolean {
|
||||||
|
const secondsRemaining = tokenSecondsRemaining(token);
|
||||||
|
return secondsRemaining < minutesBeforeExpiration * 60;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why:** Standalone utility avoids service dependencies, can be tested independently, reusable.
|
||||||
|
|
||||||
|
### Step 2: Create DC TokenService
|
||||||
|
|
||||||
|
Create `src/abstractions/token.service.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface TokenService {
|
||||||
|
// Token storage
|
||||||
|
setTokens(
|
||||||
|
accessToken: string,
|
||||||
|
refreshToken: string,
|
||||||
|
clientIdClientSecret?: [string, string],
|
||||||
|
): Promise<void>;
|
||||||
|
getToken(): Promise<string | null>;
|
||||||
|
getRefreshToken(): Promise<string | null>;
|
||||||
|
clearToken(): Promise<void>;
|
||||||
|
|
||||||
|
// API key authentication
|
||||||
|
getClientId(): Promise<string | null>;
|
||||||
|
getClientSecret(): Promise<string | null>;
|
||||||
|
|
||||||
|
// Two-factor token (rarely used)
|
||||||
|
getTwoFactorToken(): Promise<string | null>;
|
||||||
|
clearTwoFactorToken(): Promise<void>;
|
||||||
|
|
||||||
|
// Token validation (delegates to jwt.util)
|
||||||
|
decodeToken(token?: string): Promise<DecodedToken | null>;
|
||||||
|
tokenNeedsRefresh(minutesBeforeExpiration?: number): Promise<boolean>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Create `src/services/token/token.service.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { StateServiceVNext } from "@/abstractions/state-vNext.service";
|
||||||
|
import { SecureStorageService } from "@/jslib/common/src/abstractions/storage.service";
|
||||||
|
import { TokenService as ITokenService } from "@/abstractions/token.service";
|
||||||
|
import {
|
||||||
|
decodeJwt,
|
||||||
|
tokenNeedsRefresh as checkTokenNeedsRefresh,
|
||||||
|
DecodedToken,
|
||||||
|
} from "@/utils/jwt.util";
|
||||||
|
|
||||||
|
export class TokenService implements ITokenService {
|
||||||
|
// Storage keys
|
||||||
|
private TOKEN_KEY = "accessToken";
|
||||||
|
private REFRESH_TOKEN_KEY = "refreshToken";
|
||||||
|
private CLIENT_ID_KEY = "apiKeyClientId";
|
||||||
|
private CLIENT_SECRET_KEY = "apiKeyClientSecret";
|
||||||
|
private TWO_FACTOR_TOKEN_KEY = "twoFactorToken";
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private stateService: StateServiceVNext,
|
||||||
|
private secureStorageService: SecureStorageService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async setTokens(
|
||||||
|
accessToken: string,
|
||||||
|
refreshToken: string,
|
||||||
|
clientIdClientSecret?: [string, string],
|
||||||
|
): Promise<void> {
|
||||||
|
await this.secureStorageService.save(this.TOKEN_KEY, accessToken);
|
||||||
|
await this.secureStorageService.save(this.REFRESH_TOKEN_KEY, refreshToken);
|
||||||
|
|
||||||
|
if (clientIdClientSecret) {
|
||||||
|
await this.secureStorageService.save(this.CLIENT_ID_KEY, clientIdClientSecret[0]);
|
||||||
|
await this.secureStorageService.save(this.CLIENT_SECRET_KEY, clientIdClientSecret[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getToken(): Promise<string | null> {
|
||||||
|
return await this.secureStorageService.get<string>(this.TOKEN_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRefreshToken(): Promise<string | null> {
|
||||||
|
return await this.secureStorageService.get<string>(this.REFRESH_TOKEN_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearToken(): Promise<void> {
|
||||||
|
await this.secureStorageService.remove(this.TOKEN_KEY);
|
||||||
|
await this.secureStorageService.remove(this.REFRESH_TOKEN_KEY);
|
||||||
|
await this.secureStorageService.remove(this.CLIENT_ID_KEY);
|
||||||
|
await this.secureStorageService.remove(this.CLIENT_SECRET_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getClientId(): Promise<string | null> {
|
||||||
|
return await this.secureStorageService.get<string>(this.CLIENT_ID_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getClientSecret(): Promise<string | null> {
|
||||||
|
return await this.secureStorageService.get<string>(this.CLIENT_SECRET_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTwoFactorToken(): Promise<string | null> {
|
||||||
|
return await this.secureStorageService.get<string>(this.TWO_FACTOR_TOKEN_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearTwoFactorToken(): Promise<void> {
|
||||||
|
await this.secureStorageService.remove(this.TWO_FACTOR_TOKEN_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
async decodeToken(token?: string): Promise<DecodedToken | null> {
|
||||||
|
const tokenToUse = token ?? (await this.getToken());
|
||||||
|
if (!tokenToUse) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return decodeJwt(tokenToUse);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async tokenNeedsRefresh(minutesBeforeExpiration = 5): Promise<boolean> {
|
||||||
|
const token = await this.getToken();
|
||||||
|
if (!token) return true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return checkTokenNeedsRefresh(token, minutesBeforeExpiration);
|
||||||
|
} catch {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Create `src/services/token/token.service.spec.ts` with comprehensive tests covering:
|
||||||
|
|
||||||
|
- Token storage/retrieval
|
||||||
|
- Token clearing
|
||||||
|
- JWT decoding
|
||||||
|
- Token expiration logic
|
||||||
|
- Error handling for malformed tokens
|
||||||
|
|
||||||
|
### Step 3: Create DC EnvironmentService
|
||||||
|
|
||||||
|
Create `src/abstractions/environment.service.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface EnvironmentUrls {
|
||||||
|
base?: string;
|
||||||
|
api?: string;
|
||||||
|
identity?: string;
|
||||||
|
webVault?: string;
|
||||||
|
icons?: string;
|
||||||
|
notifications?: string;
|
||||||
|
events?: string;
|
||||||
|
keyConnector?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EnvironmentService {
|
||||||
|
setUrls(urls: EnvironmentUrls): Promise<void>;
|
||||||
|
setUrlsFromStorage(): Promise<void>;
|
||||||
|
|
||||||
|
hasBaseUrl(): boolean;
|
||||||
|
getApiUrl(): string;
|
||||||
|
getIdentityUrl(): string;
|
||||||
|
getWebVaultUrl(): string;
|
||||||
|
getIconsUrl(): string;
|
||||||
|
getNotificationsUrl(): string;
|
||||||
|
getEventsUrl(): string;
|
||||||
|
getKeyConnectorUrl(): string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Create `src/services/environment/environment.service.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { StateServiceVNext } from "@/abstractions/state-vNext.service";
|
||||||
|
import {
|
||||||
|
EnvironmentService as IEnvironmentService,
|
||||||
|
EnvironmentUrls,
|
||||||
|
} from "@/abstractions/environment.service";
|
||||||
|
|
||||||
|
export class EnvironmentService implements IEnvironmentService {
|
||||||
|
private readonly DEFAULT_URLS = {
|
||||||
|
api: "https://api.bitwarden.com",
|
||||||
|
identity: "https://identity.bitwarden.com",
|
||||||
|
webVault: "https://vault.bitwarden.com",
|
||||||
|
icons: "https://icons.bitwarden.net",
|
||||||
|
notifications: "https://notifications.bitwarden.com",
|
||||||
|
events: "https://events.bitwarden.com",
|
||||||
|
};
|
||||||
|
|
||||||
|
private urls: EnvironmentUrls = {};
|
||||||
|
|
||||||
|
constructor(private stateService: StateServiceVNext) {}
|
||||||
|
|
||||||
|
async setUrls(urls: EnvironmentUrls): Promise<void> {
|
||||||
|
// Normalize URLs: trim whitespace, remove trailing slashes, add https:// if missing
|
||||||
|
const normalized: EnvironmentUrls = {};
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(urls)) {
|
||||||
|
if (!value) continue;
|
||||||
|
|
||||||
|
let url = value.trim();
|
||||||
|
url = url.replace(/\/+$/, ""); // Remove trailing slashes
|
||||||
|
|
||||||
|
if (!/^https?:\/\//i.test(url)) {
|
||||||
|
url = `https://${url}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized[key] = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.urls = normalized;
|
||||||
|
await this.stateService.setEnvironmentUrls(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setUrlsFromStorage(): Promise<void> {
|
||||||
|
const stored = await this.stateService.getEnvironmentUrls();
|
||||||
|
this.urls = stored ?? {};
|
||||||
|
}
|
||||||
|
|
||||||
|
hasBaseUrl(): boolean {
|
||||||
|
return !!this.urls.base;
|
||||||
|
}
|
||||||
|
|
||||||
|
getApiUrl(): string {
|
||||||
|
return this.urls.api ?? this.urls.base + "/api" ?? this.DEFAULT_URLS.api;
|
||||||
|
}
|
||||||
|
|
||||||
|
getIdentityUrl(): string {
|
||||||
|
return this.urls.identity ?? this.urls.base + "/identity" ?? this.DEFAULT_URLS.identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
getWebVaultUrl(): string {
|
||||||
|
return this.urls.webVault ?? this.urls.base ?? this.DEFAULT_URLS.webVault;
|
||||||
|
}
|
||||||
|
|
||||||
|
getIconsUrl(): string {
|
||||||
|
return this.urls.icons ?? this.urls.base + "/icons" ?? this.DEFAULT_URLS.icons;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNotificationsUrl(): string {
|
||||||
|
return (
|
||||||
|
this.urls.notifications ??
|
||||||
|
this.urls.base + "/notifications" ??
|
||||||
|
this.DEFAULT_URLS.notifications
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getEventsUrl(): string {
|
||||||
|
return this.urls.events ?? this.urls.base + "/events" ?? this.DEFAULT_URLS.events;
|
||||||
|
}
|
||||||
|
|
||||||
|
getKeyConnectorUrl(): string {
|
||||||
|
return this.urls.keyConnector ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Create `src/services/environment/environment.service.spec.ts` with tests covering:
|
||||||
|
|
||||||
|
- URL normalization (trailing slashes, https prefix)
|
||||||
|
- Storage persistence
|
||||||
|
- Default URL fallbacks
|
||||||
|
- Custom URL override
|
||||||
|
- Base URL derivation
|
||||||
|
|
||||||
|
### Step 4: Add Environment URL Storage to StateServiceVNext
|
||||||
|
|
||||||
|
Update `src/models/state.model.ts` to add environment URL storage key:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const StorageKeysVNext = {
|
||||||
|
// ... existing keys ...
|
||||||
|
environmentUrls: "environmentUrls",
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Update `src/abstractions/state-vNext.service.ts` to add methods:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface StateServiceVNext {
|
||||||
|
// ... existing methods ...
|
||||||
|
getEnvironmentUrls(): Promise<EnvironmentUrls | null>;
|
||||||
|
setEnvironmentUrls(urls: EnvironmentUrls): Promise<void>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Update `src/services/state-service/state-vNext.service.ts` implementation to add storage methods.
|
||||||
|
|
||||||
|
### Step 5: Update StateMigrationService for Token/Environment Data
|
||||||
|
|
||||||
|
Update `src/services/state-service/stateMigration.service.ts` to migrate:
|
||||||
|
|
||||||
|
**Token data (from secure storage):**
|
||||||
|
|
||||||
|
- `accessToken` → `accessToken` (same key, no change needed)
|
||||||
|
- `refreshToken` → `refreshToken` (same key, no change needed)
|
||||||
|
- `apiKeyClientId` → `apiKeyClientId` (same key, no change needed)
|
||||||
|
- `apiKeyClientSecret` → `apiKeyClientSecret` (same key, no change needed)
|
||||||
|
|
||||||
|
**Environment URLs (from account state):**
|
||||||
|
|
||||||
|
- `environmentUrls` from account → `environmentUrls` in flat structure
|
||||||
|
|
||||||
|
Add migration test cases to `src/services/state-service/stateMigration.service.spec.ts`.
|
||||||
|
|
||||||
|
### Step 6: Remove CryptoService Dependencies
|
||||||
|
|
||||||
|
Since CryptoService is completely unused by DC:
|
||||||
|
|
||||||
|
1. Search for all imports of `CryptoService` in `src/` code
|
||||||
|
2. Remove all instantiations and injections
|
||||||
|
3. Verify no methods are actually called
|
||||||
|
4. Remove from DI containers (services.module.ts, bwdc.ts, main.ts)
|
||||||
|
|
||||||
|
Expected: Zero usage, straightforward removal.
|
||||||
|
|
||||||
|
### Step 7: Update WindowMain/TrayMain to Use StateServiceVNext
|
||||||
|
|
||||||
|
Update `jslib/electron/src/window.main.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Change constructor to accept StateServiceVNext instead of StateService
|
||||||
|
constructor(
|
||||||
|
private stateService: StateServiceVNext, // Changed from StateService
|
||||||
|
// ... other params
|
||||||
|
) {}
|
||||||
|
|
||||||
|
// Update method calls to use StateServiceVNext interface
|
||||||
|
async getWindowSettings(): Promise<any> {
|
||||||
|
return await this.stateService.getWindowSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
async setWindowSettings(settings: any): Promise<void> {
|
||||||
|
await this.stateService.setWindowSettings(settings);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Update `jslib/electron/src/tray.main.ts` similarly:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
constructor(
|
||||||
|
private stateService: StateServiceVNext, // Changed from StateService
|
||||||
|
// ... other params
|
||||||
|
) {}
|
||||||
|
|
||||||
|
// Update method calls
|
||||||
|
async getEnableTray(): Promise<boolean> {
|
||||||
|
return await this.stateService.getEnableTray();
|
||||||
|
}
|
||||||
|
// ... etc for other tray settings
|
||||||
|
```
|
||||||
|
|
||||||
|
**Required:** Add window/tray setting storage to StateServiceVNext:
|
||||||
|
|
||||||
|
- `getWindowSettings()` / `setWindowSettings()`
|
||||||
|
- `getEnableTray()` / `getEnableMinimizeToTray()` / `getEnableCloseToTray()` / `getAlwaysShowDock()`
|
||||||
|
|
||||||
|
### Step 8: Update Service Registrations
|
||||||
|
|
||||||
|
**In `src/app/services/services.module.ts`:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Remove old services
|
||||||
|
- import { StateService } from '@/services/state-service/state.service';
|
||||||
|
- import { TokenService } from '@/jslib/common/src/services/token.service';
|
||||||
|
- import { CryptoService } from '@/jslib/common/src/services/crypto.service';
|
||||||
|
- import { EnvironmentService } from '@/jslib/common/src/services/environment.service';
|
||||||
|
|
||||||
|
// Add new services
|
||||||
|
+ import { TokenService } from '@/services/token/token.service';
|
||||||
|
+ import { EnvironmentService } from '@/services/environment/environment.service';
|
||||||
|
|
||||||
|
providers: [
|
||||||
|
// Remove old StateService provider
|
||||||
|
- { provide: StateService, useClass: StateService },
|
||||||
|
|
||||||
|
// Add new service providers
|
||||||
|
+ { provide: TokenService, useClass: TokenService },
|
||||||
|
+ { provide: EnvironmentService, useClass: EnvironmentService },
|
||||||
|
|
||||||
|
// Keep StateServiceVNext
|
||||||
|
{ provide: StateServiceVNext, useClass: StateServiceVNextImplementation },
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**In `src/bwdc.ts` (CLI):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Remove old service instantiations
|
||||||
|
- this.stateService = new StateService(/* ... */);
|
||||||
|
- this.cryptoService = new CryptoService(/* ... */);
|
||||||
|
- this.tokenService = new TokenService(/* ... */);
|
||||||
|
- this.environmentService = new EnvironmentService(this.stateService);
|
||||||
|
|
||||||
|
// Add new service instantiations
|
||||||
|
+ this.tokenService = new TokenService(this.stateServiceVNext, secureStorageService);
|
||||||
|
+ this.environmentService = new EnvironmentService(this.stateServiceVNext);
|
||||||
|
```
|
||||||
|
|
||||||
|
**In `src/main.ts` (Electron):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Remove old service instantiations
|
||||||
|
- this.stateService = new StateService(/* ... */);
|
||||||
|
- this.cryptoService = new CryptoService(/* ... */);
|
||||||
|
- this.tokenService = new TokenService(/* ... */);
|
||||||
|
- this.environmentService = new EnvironmentService(this.stateService);
|
||||||
|
|
||||||
|
// Add new service instantiations
|
||||||
|
+ this.tokenService = new TokenService(this.stateServiceVNext, secureStorageService);
|
||||||
|
+ this.environmentService = new EnvironmentService(this.stateServiceVNext);
|
||||||
|
|
||||||
|
// Update WindowMain/TrayMain to use StateServiceVNext
|
||||||
|
- this.windowMain = new WindowMain(this.stateService, /* ... */);
|
||||||
|
- this.trayMain = new TrayMain(this.stateService, /* ... */);
|
||||||
|
+ this.windowMain = new WindowMain(this.stateServiceVNext, /* ... */);
|
||||||
|
+ this.trayMain = new TrayMain(this.stateServiceVNext, /* ... */);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 9: Update Import Statements
|
||||||
|
|
||||||
|
Update all files that import Token/Environment services:
|
||||||
|
|
||||||
|
**Files to update:**
|
||||||
|
|
||||||
|
- `src/services/api.service.ts` - Change TokenService import to DC version
|
||||||
|
- `src/services/auth.service.ts` - Change EnvironmentService import to DC version
|
||||||
|
- `src/services/sync.service.ts` - Change EnvironmentService import to DC version
|
||||||
|
- `src/commands/config.command.ts` - Change EnvironmentService import to DC version
|
||||||
|
- `src/app/app.component.ts` - Change TokenService import to DC version
|
||||||
|
|
||||||
|
**Pattern:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Before
|
||||||
|
import { TokenService } from "@/jslib/common/src/services/token.service";
|
||||||
|
import { EnvironmentService } from "@/jslib/common/src/services/environment.service";
|
||||||
|
|
||||||
|
// After
|
||||||
|
import { TokenService } from "@/abstractions/token.service";
|
||||||
|
import { EnvironmentService } from "@/abstractions/environment.service";
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 10: Delete Old StateService and jslib Services
|
||||||
|
|
||||||
|
**Delete these files (after all references removed):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Old StateService implementations
|
||||||
|
src/services/state-service/state.service.ts
|
||||||
|
src/abstractions/state.service.ts
|
||||||
|
jslib/common/src/services/state.service.ts
|
||||||
|
jslib/common/src/abstractions/state.service.ts
|
||||||
|
|
||||||
|
# jslib Token/Crypto/Environment services
|
||||||
|
jslib/common/src/services/token.service.ts
|
||||||
|
jslib/common/src/abstractions/token.service.ts
|
||||||
|
jslib/common/src/services/crypto.service.ts
|
||||||
|
jslib/common/src/abstractions/crypto.service.ts
|
||||||
|
jslib/common/src/services/environment.service.ts
|
||||||
|
jslib/common/src/abstractions/environment.service.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rename StateServiceVNext to StateService:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Rename files
|
||||||
|
mv src/services/state-service/state-vNext.service.ts src/services/state-service/state.service.ts
|
||||||
|
mv src/services/state-service/state-vNext.service.spec.ts src/services/state-service/state.service.spec.ts
|
||||||
|
mv src/abstractions/state-vNext.service.ts src/abstractions/state.service.ts
|
||||||
|
|
||||||
|
# Update all imports from StateServiceVNext to StateService
|
||||||
|
# Find and replace: StateServiceVNext → StateService
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 11: Update Tests
|
||||||
|
|
||||||
|
**Update existing tests that mock StateService:**
|
||||||
|
|
||||||
|
- Update mocks to use new StateService interface (flat key-value structure)
|
||||||
|
- Remove mocks for Token/Crypto/Environment services where they inject old versions
|
||||||
|
- Add mocks for new DC Token/Environment services
|
||||||
|
|
||||||
|
**Add new test files:**
|
||||||
|
|
||||||
|
- `src/services/token/token.service.spec.ts` (created in Step 2)
|
||||||
|
- `src/services/environment/environment.service.spec.ts` (created in Step 3)
|
||||||
|
- `src/utils/jwt.util.spec.ts` (JWT utility tests)
|
||||||
|
|
||||||
|
**Update integration tests:**
|
||||||
|
|
||||||
|
- Verify token storage/retrieval works correctly
|
||||||
|
- Verify environment URL configuration persists
|
||||||
|
- Verify window/tray settings persist in Electron app
|
||||||
|
|
||||||
|
## Verification Plan
|
||||||
|
|
||||||
|
### Unit Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm test # Run all unit tests
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected:**
|
||||||
|
|
||||||
|
- All new service tests pass (TokenService, EnvironmentService, JWT util)
|
||||||
|
- All existing tests pass with updated mocks
|
||||||
|
- No test failures due to StateService removal
|
||||||
|
|
||||||
|
### Integration Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run test:integration
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected:**
|
||||||
|
|
||||||
|
- LDAP sync tests pass
|
||||||
|
- Authentication flow works correctly
|
||||||
|
- Configuration persistence works
|
||||||
|
|
||||||
|
### Manual Testing - CLI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build and run CLI
|
||||||
|
npm run build:cli:watch
|
||||||
|
node ./build-cli/bwdc.js --help
|
||||||
|
|
||||||
|
# Test authentication
|
||||||
|
node ./build-cli/bwdc.js config server https://vault.bitwarden.com
|
||||||
|
node ./build-cli/bwdc.js login --apikey
|
||||||
|
|
||||||
|
# Test sync
|
||||||
|
node ./build-cli/bwdc.js config directory ldap
|
||||||
|
node ./build-cli/bwdc.js config ldap.hostname ldap.example.com
|
||||||
|
node ./build-cli/bwdc.js sync
|
||||||
|
|
||||||
|
# Test logout
|
||||||
|
node ./build-cli/bwdc.js logout
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verify:**
|
||||||
|
|
||||||
|
- ✅ Server URL configuration persists
|
||||||
|
- ✅ Login stores tokens correctly
|
||||||
|
- ✅ Token refresh works automatically
|
||||||
|
- ✅ Sync completes successfully
|
||||||
|
- ✅ Logout clears tokens
|
||||||
|
|
||||||
|
### Manual Testing - Desktop App
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build and run desktop app
|
||||||
|
npm run electron
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verify:**
|
||||||
|
|
||||||
|
- ✅ Window position/size persists across restarts
|
||||||
|
- ✅ "Always on top" setting persists
|
||||||
|
- ✅ Tray icon shows/hides based on settings
|
||||||
|
- ✅ Minimize/close to tray works
|
||||||
|
- ✅ Login/logout flow works
|
||||||
|
- ✅ Sync functionality works
|
||||||
|
- ✅ Custom server URL configuration works
|
||||||
|
|
||||||
|
### Migration Testing
|
||||||
|
|
||||||
|
**Test migration from existing installation:**
|
||||||
|
|
||||||
|
1. Install current production version
|
||||||
|
2. Configure directory connection and run sync
|
||||||
|
3. Install new version with StateService removal
|
||||||
|
4. Launch app - verify automatic migration occurs
|
||||||
|
5. Verify all settings preserved:
|
||||||
|
- Directory configuration
|
||||||
|
- Organization ID
|
||||||
|
- Server URLs
|
||||||
|
- Window/tray settings
|
||||||
|
- Authentication tokens
|
||||||
|
|
||||||
|
**Expected:**
|
||||||
|
|
||||||
|
- ✅ Migration runs automatically on first launch
|
||||||
|
- ✅ All user data preserved
|
||||||
|
- ✅ No user action required
|
||||||
|
- ✅ App functions identically to before
|
||||||
|
|
||||||
|
### Regression Testing
|
||||||
|
|
||||||
|
Run through all major workflows:
|
||||||
|
|
||||||
|
1. **Configuration**: Set up each directory type (LDAP, Entra, Google, Okta, OneLogin)
|
||||||
|
2. **Authentication**: Login with API key, verify token refresh
|
||||||
|
3. **Sync**: Full sync, incremental sync (delta tokens), detect changes via hash
|
||||||
|
4. **Custom server**: Configure self-hosted Bitwarden server
|
||||||
|
5. **Electron features**: Window management, tray behavior
|
||||||
|
|
||||||
|
**Expected:** No regressions in functionality.
|
||||||
|
|
||||||
|
## Rollback Plan
|
||||||
|
|
||||||
|
If critical issues discovered post-deployment:
|
||||||
|
|
||||||
|
1. **Revert commit** removing StateService
|
||||||
|
2. **Keep StateServiceVNext in parallel** (already coexisting)
|
||||||
|
3. **Debug issues** in development
|
||||||
|
4. **Re-attempt removal** after fixes
|
||||||
|
|
||||||
|
**Risk Assessment:** Low - StateServiceVNext has been in production since Phase 1 PR merge, proven stable.
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
- ✅ All old StateService implementations deleted
|
||||||
|
- ✅ StateServiceVNext renamed to StateService (becomes primary)
|
||||||
|
- ✅ jslib TokenService, CryptoService, EnvironmentService deleted
|
||||||
|
- ✅ DC-specific Token/Environment services implemented and tested
|
||||||
|
- ✅ All unit tests pass
|
||||||
|
- ✅ All integration tests pass
|
||||||
|
- ✅ Manual testing shows no regressions
|
||||||
|
- ✅ Migration from old state structure works automatically
|
||||||
|
- ✅ WindowMain/TrayMain adapted to new StateService
|
||||||
|
- ✅ Zero references to old StateService in codebase
|
||||||
|
|
||||||
|
## Next Steps (After Completion)
|
||||||
|
|
||||||
|
This unblocks **Phase 2: Remove Remaining jslib Code**:
|
||||||
|
|
||||||
|
- Delete unused jslib models (AccountData, AccountSettings, etc.)
|
||||||
|
- Delete unused jslib services that referenced StateService
|
||||||
|
- Clean up jslib/common folder of unused client code
|
||||||
|
- Potentially merge remaining jslib code into src/ (flatten structure)
|
||||||
|
|
||||||
|
Estimated effort: 2-3 days for experienced developer familiar with codebase.
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { webcrypto } from "crypto";
|
|
||||||
import "jest-preset-angular/setup-jest";
|
|
||||||
|
|
||||||
Object.defineProperty(window, "CSS", { value: null });
|
|
||||||
Object.defineProperty(window, "getComputedStyle", {
|
|
||||||
value: () => {
|
|
||||||
return {
|
|
||||||
display: "none",
|
|
||||||
appearance: ["-webkit-appearance"],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.defineProperty(document, "doctype", {
|
|
||||||
value: "<!DOCTYPE html>",
|
|
||||||
});
|
|
||||||
Object.defineProperty(document.body.style, "transform", {
|
|
||||||
value: () => {
|
|
||||||
return {
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.defineProperty(window, "crypto", {
|
|
||||||
value: webcrypto,
|
|
||||||
});
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import { Directive, EventEmitter, Output } from "@angular/core";
|
|
||||||
|
|
||||||
import { EnvironmentService } from "@/jslib/common/src/abstractions/environment.service";
|
|
||||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
|
||||||
import { PlatformUtilsService } from "@/jslib/common/src/abstractions/platformUtils.service";
|
|
||||||
|
|
||||||
@Directive()
|
|
||||||
export class EnvironmentComponent {
|
|
||||||
@Output() onSaved = new EventEmitter();
|
|
||||||
|
|
||||||
iconsUrl: string;
|
|
||||||
identityUrl: string;
|
|
||||||
apiUrl: string;
|
|
||||||
webVaultUrl: string;
|
|
||||||
notificationsUrl: string;
|
|
||||||
baseUrl: string;
|
|
||||||
showCustom = false;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
protected platformUtilsService: PlatformUtilsService,
|
|
||||||
protected environmentService: EnvironmentService,
|
|
||||||
protected i18nService: I18nService,
|
|
||||||
) {
|
|
||||||
const urls = this.environmentService.getUrls();
|
|
||||||
|
|
||||||
this.baseUrl = urls.base || "";
|
|
||||||
this.webVaultUrl = urls.webVault || "";
|
|
||||||
this.apiUrl = urls.api || "";
|
|
||||||
this.identityUrl = urls.identity || "";
|
|
||||||
this.iconsUrl = urls.icons || "";
|
|
||||||
this.notificationsUrl = urls.notifications || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
async submit() {
|
|
||||||
const resUrls = await this.environmentService.setUrls({
|
|
||||||
base: this.baseUrl,
|
|
||||||
api: this.apiUrl,
|
|
||||||
identity: this.identityUrl,
|
|
||||||
webVault: this.webVaultUrl,
|
|
||||||
icons: this.iconsUrl,
|
|
||||||
notifications: this.notificationsUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
// re-set urls since service can change them, ex: prefixing https://
|
|
||||||
this.baseUrl = resUrls.base;
|
|
||||||
this.apiUrl = resUrls.api;
|
|
||||||
this.identityUrl = resUrls.identity;
|
|
||||||
this.webVaultUrl = resUrls.webVault;
|
|
||||||
this.iconsUrl = resUrls.icons;
|
|
||||||
this.notificationsUrl = resUrls.notifications;
|
|
||||||
|
|
||||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("environmentSaved"));
|
|
||||||
this.saved();
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleCustom() {
|
|
||||||
this.showCustom = !this.showCustom;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected saved() {
|
|
||||||
this.onSaved.emit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +1,77 @@
|
|||||||
import { animate, state, style, transition, trigger } from "@angular/animations";
|
|
||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from "@angular/common";
|
||||||
import { Component, ModuleWithProviders, NgModule } from "@angular/core";
|
import { Component, ModuleWithProviders, NgModule } from "@angular/core";
|
||||||
import {
|
import { DefaultNoComponentGlobalConfig, GlobalConfig, Toast, TOAST_CONFIG } from "ngx-toastr";
|
||||||
DefaultNoComponentGlobalConfig,
|
|
||||||
GlobalConfig,
|
|
||||||
Toast as BaseToast,
|
|
||||||
ToastPackage,
|
|
||||||
ToastrService,
|
|
||||||
TOAST_CONFIG,
|
|
||||||
} from "ngx-toastr";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "[toast-component2]",
|
selector: "[toast-component2]",
|
||||||
template: `
|
template: `
|
||||||
<button
|
@if (options().closeButton) {
|
||||||
*ngIf="options.closeButton"
|
<button (click)="remove()" type="button" class="toast-close-button" aria-label="Close">
|
||||||
(click)="remove()"
|
<span aria-hidden="true">×</span>
|
||||||
type="button"
|
</button>
|
||||||
class="toast-close-button"
|
}
|
||||||
aria-label="Close"
|
|
||||||
>
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<i></i>
|
<i></i>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div *ngIf="title" [class]="options.titleClass" [attr.aria-label]="title">
|
@if (title()) {
|
||||||
{{ title }} <ng-container *ngIf="duplicatesCount">[{{ duplicatesCount + 1 }}]</ng-container>
|
<div [class]="options().titleClass" [attr.aria-label]="title()">
|
||||||
</div>
|
{{ title() }}
|
||||||
<div
|
@if (duplicatesCount) {
|
||||||
*ngIf="message && options.enableHtml"
|
[{{ duplicatesCount + 1 }}]
|
||||||
role="alertdialog"
|
}
|
||||||
aria-live="polite"
|
</div>
|
||||||
[class]="options.messageClass"
|
}
|
||||||
[innerHTML]="message"
|
@if (message() && options().enableHtml) {
|
||||||
></div>
|
<div
|
||||||
<div
|
role="alertdialog"
|
||||||
*ngIf="message && !options.enableHtml"
|
aria-live="polite"
|
||||||
role="alertdialog"
|
[class]="options().messageClass"
|
||||||
aria-live="polite"
|
[innerHTML]="message()"
|
||||||
[class]="options.messageClass"
|
></div>
|
||||||
[attr.aria-label]="message"
|
}
|
||||||
>
|
@if (message() && !options().enableHtml) {
|
||||||
{{ message }}
|
<div
|
||||||
</div>
|
role="alertdialog"
|
||||||
</div>
|
aria-live="polite"
|
||||||
<div *ngIf="options.progressBar">
|
[class]="options().messageClass"
|
||||||
<div class="toast-progress" [style.width]="width + '%'"></div>
|
[attr.aria-label]="message()"
|
||||||
|
>
|
||||||
|
{{ message() }}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</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;
|
||||||
|
}
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
animations: [
|
|
||||||
trigger("flyInOut", [
|
|
||||||
state("inactive", style({ opacity: 0 })),
|
|
||||||
state("active", style({ opacity: 1 })),
|
|
||||||
state("removed", style({ opacity: 0 })),
|
|
||||||
transition("inactive => active", animate("{{ easeTime }}ms {{ easing }}")),
|
|
||||||
transition("active => removed", animate("{{ easeTime }}ms {{ easing }}")),
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
preserveWhitespaces: false,
|
preserveWhitespaces: false,
|
||||||
standalone: false,
|
standalone: false,
|
||||||
})
|
})
|
||||||
export class BitwardenToast extends BaseToast {
|
export class BitwardenToast extends Toast {}
|
||||||
constructor(
|
|
||||||
protected toastrService: ToastrService,
|
|
||||||
public toastPackage: ToastPackage,
|
|
||||||
) {
|
|
||||||
super(toastrService, toastPackage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const BitwardenToastGlobalConfig: GlobalConfig = {
|
export const BitwardenToastGlobalConfig: GlobalConfig = {
|
||||||
...DefaultNoComponentGlobalConfig,
|
...DefaultNoComponentGlobalConfig,
|
||||||
|
|||||||
@@ -1,143 +0,0 @@
|
|||||||
import { LOCALE_ID, NgModule } from "@angular/core";
|
|
||||||
|
|
||||||
import { ApiService as ApiServiceAbstraction } from "@/jslib/common/src/abstractions/api.service";
|
|
||||||
import { AppIdService as AppIdServiceAbstraction } from "@/jslib/common/src/abstractions/appId.service";
|
|
||||||
import { BroadcasterService as BroadcasterServiceAbstraction } from "@/jslib/common/src/abstractions/broadcaster.service";
|
|
||||||
import { CryptoService as CryptoServiceAbstraction } from "@/jslib/common/src/abstractions/crypto.service";
|
|
||||||
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@/jslib/common/src/abstractions/cryptoFunction.service";
|
|
||||||
import { EnvironmentService as EnvironmentServiceAbstraction } from "@/jslib/common/src/abstractions/environment.service";
|
|
||||||
import { I18nService as I18nServiceAbstraction } from "@/jslib/common/src/abstractions/i18n.service";
|
|
||||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
|
||||||
import { MessagingService as MessagingServiceAbstraction } from "@/jslib/common/src/abstractions/messaging.service";
|
|
||||||
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@/jslib/common/src/abstractions/platformUtils.service";
|
|
||||||
import { StateService as StateServiceAbstraction } from "@/jslib/common/src/abstractions/state.service";
|
|
||||||
import { StateMigrationService as StateMigrationServiceAbstraction } from "@/jslib/common/src/abstractions/stateMigration.service";
|
|
||||||
import { StorageService as StorageServiceAbstraction } from "@/jslib/common/src/abstractions/storage.service";
|
|
||||||
import { TokenService as TokenServiceAbstraction } from "@/jslib/common/src/abstractions/token.service";
|
|
||||||
import { StateFactory } from "@/jslib/common/src/factories/stateFactory";
|
|
||||||
import { Account } from "@/jslib/common/src/models/domain/account";
|
|
||||||
import { GlobalState } from "@/jslib/common/src/models/domain/globalState";
|
|
||||||
import { ApiService } from "@/jslib/common/src/services/api.service";
|
|
||||||
import { AppIdService } from "@/jslib/common/src/services/appId.service";
|
|
||||||
import { ConsoleLogService } from "@/jslib/common/src/services/consoleLog.service";
|
|
||||||
import { CryptoService } from "@/jslib/common/src/services/crypto.service";
|
|
||||||
import { EnvironmentService } from "@/jslib/common/src/services/environment.service";
|
|
||||||
import { StateService } from "@/jslib/common/src/services/state.service";
|
|
||||||
import { StateMigrationService } from "@/jslib/common/src/services/stateMigration.service";
|
|
||||||
import { TokenService } from "@/jslib/common/src/services/token.service";
|
|
||||||
|
|
||||||
import {
|
|
||||||
SafeInjectionToken,
|
|
||||||
SECURE_STORAGE,
|
|
||||||
WINDOW,
|
|
||||||
} from "../../../../src/app/services/injection-tokens";
|
|
||||||
import { SafeProvider, safeProvider } from "../../../../src/app/services/safe-provider";
|
|
||||||
|
|
||||||
import { BroadcasterService } from "./broadcaster.service";
|
|
||||||
import { ModalService } from "./modal.service";
|
|
||||||
import { ValidationService } from "./validation.service";
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [],
|
|
||||||
providers: [
|
|
||||||
safeProvider({ provide: WINDOW, useValue: window }),
|
|
||||||
safeProvider({
|
|
||||||
provide: LOCALE_ID as SafeInjectionToken<string>,
|
|
||||||
useFactory: (i18nService: I18nServiceAbstraction) => i18nService.translationLocale,
|
|
||||||
deps: [I18nServiceAbstraction],
|
|
||||||
}),
|
|
||||||
safeProvider(ValidationService),
|
|
||||||
safeProvider(ModalService),
|
|
||||||
safeProvider({
|
|
||||||
provide: AppIdServiceAbstraction,
|
|
||||||
useClass: AppIdService,
|
|
||||||
deps: [StorageServiceAbstraction],
|
|
||||||
}),
|
|
||||||
safeProvider({ provide: LogService, useFactory: () => new ConsoleLogService(false), deps: [] }),
|
|
||||||
safeProvider({
|
|
||||||
provide: EnvironmentServiceAbstraction,
|
|
||||||
useClass: EnvironmentService,
|
|
||||||
deps: [StateServiceAbstraction],
|
|
||||||
}),
|
|
||||||
safeProvider({
|
|
||||||
provide: TokenServiceAbstraction,
|
|
||||||
useClass: TokenService,
|
|
||||||
deps: [StateServiceAbstraction],
|
|
||||||
}),
|
|
||||||
safeProvider({
|
|
||||||
provide: CryptoServiceAbstraction,
|
|
||||||
useClass: CryptoService,
|
|
||||||
deps: [
|
|
||||||
CryptoFunctionServiceAbstraction,
|
|
||||||
PlatformUtilsServiceAbstraction,
|
|
||||||
LogService,
|
|
||||||
StateServiceAbstraction,
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
safeProvider({
|
|
||||||
provide: ApiServiceAbstraction,
|
|
||||||
useFactory: (
|
|
||||||
tokenService: TokenServiceAbstraction,
|
|
||||||
platformUtilsService: PlatformUtilsServiceAbstraction,
|
|
||||||
environmentService: EnvironmentServiceAbstraction,
|
|
||||||
messagingService: MessagingServiceAbstraction,
|
|
||||||
appIdService: AppIdServiceAbstraction,
|
|
||||||
) =>
|
|
||||||
new ApiService(
|
|
||||||
tokenService,
|
|
||||||
platformUtilsService,
|
|
||||||
environmentService,
|
|
||||||
appIdService,
|
|
||||||
async (expired: boolean) => messagingService.send("logout", { expired: expired }),
|
|
||||||
),
|
|
||||||
deps: [
|
|
||||||
TokenServiceAbstraction,
|
|
||||||
PlatformUtilsServiceAbstraction,
|
|
||||||
EnvironmentServiceAbstraction,
|
|
||||||
MessagingServiceAbstraction,
|
|
||||||
AppIdServiceAbstraction,
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
safeProvider({
|
|
||||||
provide: BroadcasterServiceAbstraction,
|
|
||||||
useClass: BroadcasterService,
|
|
||||||
useAngularDecorators: true,
|
|
||||||
}),
|
|
||||||
safeProvider({
|
|
||||||
provide: StateServiceAbstraction,
|
|
||||||
useFactory: (
|
|
||||||
storageService: StorageServiceAbstraction,
|
|
||||||
secureStorageService: StorageServiceAbstraction,
|
|
||||||
logService: LogService,
|
|
||||||
stateMigrationService: StateMigrationServiceAbstraction,
|
|
||||||
) =>
|
|
||||||
new StateService(
|
|
||||||
storageService,
|
|
||||||
secureStorageService,
|
|
||||||
logService,
|
|
||||||
stateMigrationService,
|
|
||||||
new StateFactory(GlobalState, Account),
|
|
||||||
),
|
|
||||||
deps: [
|
|
||||||
StorageServiceAbstraction,
|
|
||||||
SECURE_STORAGE,
|
|
||||||
LogService,
|
|
||||||
StateMigrationServiceAbstraction,
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
safeProvider({
|
|
||||||
provide: StateMigrationServiceAbstraction,
|
|
||||||
useFactory: (
|
|
||||||
storageService: StorageServiceAbstraction,
|
|
||||||
secureStorageService: StorageServiceAbstraction,
|
|
||||||
) =>
|
|
||||||
new StateMigrationService(
|
|
||||||
storageService,
|
|
||||||
secureStorageService,
|
|
||||||
new StateFactory(GlobalState, Account),
|
|
||||||
),
|
|
||||||
deps: [StorageServiceAbstraction, SECURE_STORAGE],
|
|
||||||
}),
|
|
||||||
] satisfies SafeProvider[],
|
|
||||||
})
|
|
||||||
export class JslibServicesModule {}
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
import { Substitute, Arg } from "@fluffy-spoon/substitute";
|
|
||||||
|
|
||||||
import { CryptoService } from "@/jslib/common/src/abstractions/crypto.service";
|
|
||||||
import { EncryptionType } from "@/jslib/common/src/enums/encryptionType";
|
|
||||||
import { EncString } from "@/jslib/common/src/models/domain/encString";
|
|
||||||
import { SymmetricCryptoKey } from "@/jslib/common/src/models/domain/symmetricCryptoKey";
|
|
||||||
import { ContainerService } from "@/jslib/common/src/services/container.service";
|
|
||||||
|
|
||||||
describe("EncString", () => {
|
|
||||||
afterEach(() => {
|
|
||||||
(window as any).bitwardenContainerService = undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Rsa2048_OaepSha256_B64", () => {
|
|
||||||
it("constructor", () => {
|
|
||||||
const encString = new EncString(EncryptionType.Rsa2048_OaepSha256_B64, "data");
|
|
||||||
|
|
||||||
expect(encString).toEqual({
|
|
||||||
data: "data",
|
|
||||||
encryptedString: "3.data",
|
|
||||||
encryptionType: 3,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("parse existing", () => {
|
|
||||||
it("valid", () => {
|
|
||||||
const encString = new EncString("3.data");
|
|
||||||
|
|
||||||
expect(encString).toEqual({
|
|
||||||
data: "data",
|
|
||||||
encryptedString: "3.data",
|
|
||||||
encryptionType: 3,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("invalid", () => {
|
|
||||||
const encString = new EncString("3.data|test");
|
|
||||||
|
|
||||||
expect(encString).toEqual({
|
|
||||||
encryptedString: "3.data|test",
|
|
||||||
encryptionType: 3,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("decrypt", () => {
|
|
||||||
const encString = new EncString(EncryptionType.Rsa2048_OaepSha256_B64, "data");
|
|
||||||
|
|
||||||
const cryptoService = Substitute.for<CryptoService>();
|
|
||||||
cryptoService.getOrgKey(null).resolves(null);
|
|
||||||
cryptoService.decryptToUtf8(encString, Arg.any()).resolves("decrypted");
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("decrypts correctly", async () => {
|
|
||||||
const decrypted = await encString.decrypt(null);
|
|
||||||
|
|
||||||
expect(decrypted).toBe("decrypted");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("result should be cached", async () => {
|
|
||||||
const decrypted = await encString.decrypt(null);
|
|
||||||
cryptoService.received(1).decryptToUtf8(Arg.any(), Arg.any());
|
|
||||||
|
|
||||||
expect(decrypted).toBe("decrypted");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("AesCbc256_B64", () => {
|
|
||||||
it("constructor", () => {
|
|
||||||
const encString = new EncString(EncryptionType.AesCbc256_B64, "data", "iv");
|
|
||||||
|
|
||||||
expect(encString).toEqual({
|
|
||||||
data: "data",
|
|
||||||
encryptedString: "0.iv|data",
|
|
||||||
encryptionType: 0,
|
|
||||||
iv: "iv",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("parse existing", () => {
|
|
||||||
it("valid", () => {
|
|
||||||
const encString = new EncString("0.iv|data");
|
|
||||||
|
|
||||||
expect(encString).toEqual({
|
|
||||||
data: "data",
|
|
||||||
encryptedString: "0.iv|data",
|
|
||||||
encryptionType: 0,
|
|
||||||
iv: "iv",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("invalid", () => {
|
|
||||||
const encString = new EncString("0.iv|data|mac");
|
|
||||||
|
|
||||||
expect(encString).toEqual({
|
|
||||||
encryptedString: "0.iv|data|mac",
|
|
||||||
encryptionType: 0,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("AesCbc256_HmacSha256_B64", () => {
|
|
||||||
it("constructor", () => {
|
|
||||||
const encString = new EncString(EncryptionType.AesCbc256_HmacSha256_B64, "data", "iv", "mac");
|
|
||||||
|
|
||||||
expect(encString).toEqual({
|
|
||||||
data: "data",
|
|
||||||
encryptedString: "2.iv|data|mac",
|
|
||||||
encryptionType: 2,
|
|
||||||
iv: "iv",
|
|
||||||
mac: "mac",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("valid", () => {
|
|
||||||
const encString = new EncString("2.iv|data|mac");
|
|
||||||
|
|
||||||
expect(encString).toEqual({
|
|
||||||
data: "data",
|
|
||||||
encryptedString: "2.iv|data|mac",
|
|
||||||
encryptionType: 2,
|
|
||||||
iv: "iv",
|
|
||||||
mac: "mac",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("invalid", () => {
|
|
||||||
const encString = new EncString("2.iv|data");
|
|
||||||
|
|
||||||
expect(encString).toEqual({
|
|
||||||
encryptedString: "2.iv|data",
|
|
||||||
encryptionType: 2,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Exit early if null", () => {
|
|
||||||
const encString = new EncString(null);
|
|
||||||
|
|
||||||
expect(encString).toEqual({
|
|
||||||
encryptedString: null,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("decrypt", () => {
|
|
||||||
it("throws exception when bitwarden container not initialized", async () => {
|
|
||||||
const encString = new EncString(null);
|
|
||||||
|
|
||||||
expect.assertions(1);
|
|
||||||
try {
|
|
||||||
await encString.decrypt(null);
|
|
||||||
} catch (e) {
|
|
||||||
expect(e.message).toEqual("global bitwardenContainerService not initialized.");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("handles value it can't decrypt", async () => {
|
|
||||||
const encString = new EncString(null);
|
|
||||||
|
|
||||||
const cryptoService = Substitute.for<CryptoService>();
|
|
||||||
cryptoService.getOrgKey(null).resolves(null);
|
|
||||||
cryptoService.decryptToUtf8(encString, Arg.any()).throws("error");
|
|
||||||
|
|
||||||
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
|
|
||||||
|
|
||||||
const decrypted = await encString.decrypt(null);
|
|
||||||
|
|
||||||
expect(decrypted).toBe("[error: cannot decrypt]");
|
|
||||||
|
|
||||||
expect(encString).toEqual({
|
|
||||||
decryptedValue: "[error: cannot decrypt]",
|
|
||||||
encryptedString: null,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("passes along key", async () => {
|
|
||||||
const encString = new EncString(null);
|
|
||||||
const key = Substitute.for<SymmetricCryptoKey>();
|
|
||||||
|
|
||||||
const cryptoService = Substitute.for<CryptoService>();
|
|
||||||
cryptoService.getOrgKey(null).resolves(null);
|
|
||||||
|
|
||||||
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
|
|
||||||
|
|
||||||
await encString.decrypt(null, key);
|
|
||||||
|
|
||||||
cryptoService.received().decryptToUtf8(encString, key);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import { EncryptionType } from "@/jslib/common/src/enums/encryptionType";
|
|
||||||
import { SymmetricCryptoKey } from "@/jslib/common/src/models/domain/symmetricCryptoKey";
|
|
||||||
|
|
||||||
import { makeStaticByteArray } from "../utils";
|
|
||||||
|
|
||||||
describe("SymmetricCryptoKey", () => {
|
|
||||||
it("errors if no key", () => {
|
|
||||||
const t = () => {
|
|
||||||
new SymmetricCryptoKey(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(t).toThrowError("Must provide key");
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("guesses encKey from key length", () => {
|
|
||||||
it("AesCbc256_B64", () => {
|
|
||||||
const key = makeStaticByteArray(32);
|
|
||||||
const cryptoKey = new SymmetricCryptoKey(key);
|
|
||||||
|
|
||||||
expect(cryptoKey).toEqual({
|
|
||||||
encKey: key,
|
|
||||||
encKeyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=",
|
|
||||||
encType: 0,
|
|
||||||
key: key,
|
|
||||||
keyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=",
|
|
||||||
macKey: null,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("AesCbc128_HmacSha256_B64", () => {
|
|
||||||
const key = makeStaticByteArray(32);
|
|
||||||
const cryptoKey = new SymmetricCryptoKey(key, EncryptionType.AesCbc128_HmacSha256_B64);
|
|
||||||
|
|
||||||
expect(cryptoKey).toEqual({
|
|
||||||
encKey: key.slice(0, 16),
|
|
||||||
encKeyB64: "AAECAwQFBgcICQoLDA0ODw==",
|
|
||||||
encType: 1,
|
|
||||||
key: key,
|
|
||||||
keyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=",
|
|
||||||
macKey: key.slice(16, 32),
|
|
||||||
macKeyB64: "EBESExQVFhcYGRobHB0eHw==",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("AesCbc256_HmacSha256_B64", () => {
|
|
||||||
const key = makeStaticByteArray(64);
|
|
||||||
const cryptoKey = new SymmetricCryptoKey(key);
|
|
||||||
|
|
||||||
expect(cryptoKey).toEqual({
|
|
||||||
encKey: key.slice(0, 32),
|
|
||||||
encKeyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=",
|
|
||||||
encType: 2,
|
|
||||||
key: key,
|
|
||||||
keyB64:
|
|
||||||
"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+Pw==",
|
|
||||||
macKey: key.slice(32, 64),
|
|
||||||
macKeyB64: "ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8=",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("unknown length", () => {
|
|
||||||
const t = () => {
|
|
||||||
new SymmetricCryptoKey(makeStaticByteArray(30));
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(t).toThrowError("Unable to determine encType.");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
import { sequentialize } from "@/jslib/common/src/misc/sequentialize";
|
|
||||||
|
|
||||||
describe("sequentialize decorator", () => {
|
|
||||||
it("should call the function once", async () => {
|
|
||||||
const foo = new Foo();
|
|
||||||
const promises = [];
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
promises.push(foo.bar(1));
|
|
||||||
}
|
|
||||||
await Promise.all(promises);
|
|
||||||
|
|
||||||
expect(foo.calls).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call the function once for each instance of the object", async () => {
|
|
||||||
const foo = new Foo();
|
|
||||||
const foo2 = new Foo();
|
|
||||||
const promises = [];
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
promises.push(foo.bar(1));
|
|
||||||
promises.push(foo2.bar(1));
|
|
||||||
}
|
|
||||||
await Promise.all(promises);
|
|
||||||
|
|
||||||
expect(foo.calls).toBe(1);
|
|
||||||
expect(foo2.calls).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call the function once with key function", async () => {
|
|
||||||
const foo = new Foo();
|
|
||||||
const promises = [];
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
promises.push(foo.baz(1));
|
|
||||||
}
|
|
||||||
await Promise.all(promises);
|
|
||||||
|
|
||||||
expect(foo.calls).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call the function again when already resolved", async () => {
|
|
||||||
const foo = new Foo();
|
|
||||||
await foo.bar(1);
|
|
||||||
expect(foo.calls).toBe(1);
|
|
||||||
await foo.bar(1);
|
|
||||||
expect(foo.calls).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call the function again when already resolved with a key function", async () => {
|
|
||||||
const foo = new Foo();
|
|
||||||
await foo.baz(1);
|
|
||||||
expect(foo.calls).toBe(1);
|
|
||||||
await foo.baz(1);
|
|
||||||
expect(foo.calls).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call the function for each argument", async () => {
|
|
||||||
const foo = new Foo();
|
|
||||||
await Promise.all([foo.bar(1), foo.bar(1), foo.bar(2), foo.bar(2), foo.bar(3), foo.bar(3)]);
|
|
||||||
expect(foo.calls).toBe(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call the function for each argument with key function", async () => {
|
|
||||||
const foo = new Foo();
|
|
||||||
await Promise.all([foo.baz(1), foo.baz(1), foo.baz(2), foo.baz(2), foo.baz(3), foo.baz(3)]);
|
|
||||||
expect(foo.calls).toBe(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return correct result for each call", async () => {
|
|
||||||
const foo = new Foo();
|
|
||||||
const allRes: number[] = [];
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
foo.bar(1).then((res) => allRes.push(res)),
|
|
||||||
foo.bar(1).then((res) => allRes.push(res)),
|
|
||||||
foo.bar(2).then((res) => allRes.push(res)),
|
|
||||||
foo.bar(2).then((res) => allRes.push(res)),
|
|
||||||
foo.bar(3).then((res) => allRes.push(res)),
|
|
||||||
foo.bar(3).then((res) => allRes.push(res)),
|
|
||||||
]);
|
|
||||||
expect(foo.calls).toBe(3);
|
|
||||||
expect(allRes.length).toBe(6);
|
|
||||||
allRes.sort();
|
|
||||||
expect(allRes).toEqual([2, 2, 4, 4, 6, 6]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return correct result for each call with key function", async () => {
|
|
||||||
const foo = new Foo();
|
|
||||||
const allRes: number[] = [];
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
foo.baz(1).then((res) => allRes.push(res)),
|
|
||||||
foo.baz(1).then((res) => allRes.push(res)),
|
|
||||||
foo.baz(2).then((res) => allRes.push(res)),
|
|
||||||
foo.baz(2).then((res) => allRes.push(res)),
|
|
||||||
foo.baz(3).then((res) => allRes.push(res)),
|
|
||||||
foo.baz(3).then((res) => allRes.push(res)),
|
|
||||||
]);
|
|
||||||
expect(foo.calls).toBe(3);
|
|
||||||
expect(allRes.length).toBe(6);
|
|
||||||
allRes.sort();
|
|
||||||
expect(allRes).toEqual([3, 3, 6, 6, 9, 9]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
class Foo {
|
|
||||||
calls = 0;
|
|
||||||
|
|
||||||
@sequentialize((args) => "bar" + args[0])
|
|
||||||
bar(a: number): Promise<number> {
|
|
||||||
this.calls++;
|
|
||||||
return new Promise((res) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
res(a * 2);
|
|
||||||
}, Math.random() * 100);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@sequentialize((args) => "baz" + args[0])
|
|
||||||
baz(a: number): Promise<number> {
|
|
||||||
this.calls++;
|
|
||||||
return new Promise((res) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
res(a * 3);
|
|
||||||
}, Math.random() * 100);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
import { sequentialize } from "@/jslib/common/src/misc/sequentialize";
|
|
||||||
import { throttle } from "@/jslib/common/src/misc/throttle";
|
|
||||||
|
|
||||||
describe("throttle decorator", () => {
|
|
||||||
it("should call the function once at a time", async () => {
|
|
||||||
const foo = new Foo();
|
|
||||||
const promises = [];
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
promises.push(foo.bar(1));
|
|
||||||
}
|
|
||||||
await Promise.all(promises);
|
|
||||||
|
|
||||||
expect(foo.calls).toBe(10);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call the function once at a time for each object", async () => {
|
|
||||||
const foo = new Foo();
|
|
||||||
const foo2 = new Foo();
|
|
||||||
const promises = [];
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
promises.push(foo.bar(1));
|
|
||||||
promises.push(foo2.bar(1));
|
|
||||||
}
|
|
||||||
await Promise.all(promises);
|
|
||||||
|
|
||||||
expect(foo.calls).toBe(10);
|
|
||||||
expect(foo2.calls).toBe(10);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call the function limit at a time", async () => {
|
|
||||||
const foo = new Foo();
|
|
||||||
const promises = [];
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
promises.push(foo.baz(1));
|
|
||||||
}
|
|
||||||
await Promise.all(promises);
|
|
||||||
|
|
||||||
expect(foo.calls).toBe(10);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call the function limit at a time for each object", async () => {
|
|
||||||
const foo = new Foo();
|
|
||||||
const foo2 = new Foo();
|
|
||||||
const promises = [];
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
promises.push(foo.baz(1));
|
|
||||||
promises.push(foo2.baz(1));
|
|
||||||
}
|
|
||||||
await Promise.all(promises);
|
|
||||||
|
|
||||||
expect(foo.calls).toBe(10);
|
|
||||||
expect(foo2.calls).toBe(10);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should work together with sequentialize", async () => {
|
|
||||||
const foo = new Foo();
|
|
||||||
const promises = [];
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
promises.push(foo.qux(Math.floor(i / 2) * 2));
|
|
||||||
}
|
|
||||||
await Promise.all(promises);
|
|
||||||
|
|
||||||
expect(foo.calls).toBe(5);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
class Foo {
|
|
||||||
calls = 0;
|
|
||||||
inflight = 0;
|
|
||||||
|
|
||||||
@throttle(1, () => "bar")
|
|
||||||
bar(a: number) {
|
|
||||||
this.calls++;
|
|
||||||
this.inflight++;
|
|
||||||
return new Promise((res) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
expect(this.inflight).toBe(1);
|
|
||||||
this.inflight--;
|
|
||||||
res(a * 2);
|
|
||||||
}, Math.random() * 10);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@throttle(5, () => "baz")
|
|
||||||
baz(a: number) {
|
|
||||||
this.calls++;
|
|
||||||
this.inflight++;
|
|
||||||
return new Promise((res) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
expect(this.inflight).toBeLessThanOrEqual(5);
|
|
||||||
this.inflight--;
|
|
||||||
res(a * 3);
|
|
||||||
}, Math.random() * 10);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@sequentialize((args) => "qux" + args[0])
|
|
||||||
@throttle(1, () => "qux")
|
|
||||||
qux(a: number) {
|
|
||||||
this.calls++;
|
|
||||||
this.inflight++;
|
|
||||||
return new Promise((res) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
expect(this.inflight).toBe(1);
|
|
||||||
this.inflight--;
|
|
||||||
res(a * 3);
|
|
||||||
}, Math.random() * 10);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
import { ConsoleLogService } from "@/jslib/common/src/services/consoleLog.service";
|
|
||||||
|
|
||||||
const originalConsole = console;
|
|
||||||
let caughtMessage: any;
|
|
||||||
|
|
||||||
declare let console: any;
|
|
||||||
|
|
||||||
export function interceptConsole(interceptions: any): object {
|
|
||||||
console = {
|
|
||||||
log: function () {
|
|
||||||
interceptions.log = arguments;
|
|
||||||
},
|
|
||||||
warn: function () {
|
|
||||||
interceptions.warn = arguments;
|
|
||||||
},
|
|
||||||
error: function () {
|
|
||||||
interceptions.error = arguments;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
return interceptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function restoreConsole() {
|
|
||||||
console = originalConsole;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("ConsoleLogService", () => {
|
|
||||||
let logService: ConsoleLogService;
|
|
||||||
beforeEach(() => {
|
|
||||||
caughtMessage = {};
|
|
||||||
interceptConsole(caughtMessage);
|
|
||||||
logService = new ConsoleLogService(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
restoreConsole();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("filters messages below the set threshold", () => {
|
|
||||||
logService = new ConsoleLogService(true, () => true);
|
|
||||||
logService.debug("debug");
|
|
||||||
logService.info("info");
|
|
||||||
logService.warning("warning");
|
|
||||||
logService.error("error");
|
|
||||||
|
|
||||||
expect(caughtMessage).toEqual({});
|
|
||||||
});
|
|
||||||
it("only writes debug messages in dev mode", () => {
|
|
||||||
logService = new ConsoleLogService(false);
|
|
||||||
|
|
||||||
logService.debug("debug message");
|
|
||||||
expect(caughtMessage.log).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("writes debug/info messages to console.log", () => {
|
|
||||||
logService.debug("this is a debug message");
|
|
||||||
expect(caughtMessage).toMatchObject({
|
|
||||||
log: { "0": "this is a debug message" },
|
|
||||||
});
|
|
||||||
|
|
||||||
logService.info("this is an info message");
|
|
||||||
expect(caughtMessage).toMatchObject({
|
|
||||||
log: { "0": "this is an info message" },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("writes warning messages to console.warn", () => {
|
|
||||||
logService.warning("this is a warning message");
|
|
||||||
expect(caughtMessage).toMatchObject({
|
|
||||||
warn: { 0: "this is a warning message" },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("writes error messages to console.error", () => {
|
|
||||||
logService.error("this is an error message");
|
|
||||||
expect(caughtMessage).toMatchObject({
|
|
||||||
error: { 0: "this is an error message" },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("times with output to info", async () => {
|
|
||||||
logService.time();
|
|
||||||
await new Promise((r) => setTimeout(r, 250));
|
|
||||||
const duration = logService.timeEnd();
|
|
||||||
expect(duration[0]).toBe(0);
|
|
||||||
expect(duration[1]).toBeGreaterThan(0);
|
|
||||||
expect(duration[1]).toBeLessThan(500 * 10e6);
|
|
||||||
|
|
||||||
expect(caughtMessage).toEqual(expect.arrayContaining([]));
|
|
||||||
expect(caughtMessage.log.length).toBe(1);
|
|
||||||
expect(caughtMessage.log[0]).toEqual(expect.stringMatching(/^default: \d+\.?\d*ms$/));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("filters time output", async () => {
|
|
||||||
logService = new ConsoleLogService(true, () => true);
|
|
||||||
logService.time();
|
|
||||||
logService.timeEnd();
|
|
||||||
|
|
||||||
expect(caughtMessage).toEqual({});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
|
||||||
|
|
||||||
import { StorageService } from "@/jslib/common/src/abstractions/storage.service";
|
|
||||||
import { StateVersion } from "@/jslib/common/src/enums/stateVersion";
|
|
||||||
import { StateFactory } from "@/jslib/common/src/factories/stateFactory";
|
|
||||||
import { Account } from "@/jslib/common/src/models/domain/account";
|
|
||||||
import { GlobalState } from "@/jslib/common/src/models/domain/globalState";
|
|
||||||
import { StateMigrationService } from "@/jslib/common/src/services/stateMigration.service";
|
|
||||||
|
|
||||||
const userId = "USER_ID";
|
|
||||||
|
|
||||||
describe("State Migration Service", () => {
|
|
||||||
let storageService: SubstituteOf<StorageService>;
|
|
||||||
let secureStorageService: SubstituteOf<StorageService>;
|
|
||||||
let stateFactory: SubstituteOf<StateFactory>;
|
|
||||||
|
|
||||||
let stateMigrationService: StateMigrationService;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
storageService = Substitute.for<StorageService>();
|
|
||||||
secureStorageService = Substitute.for<StorageService>();
|
|
||||||
stateFactory = Substitute.for<StateFactory>();
|
|
||||||
|
|
||||||
stateMigrationService = new StateMigrationService(
|
|
||||||
storageService,
|
|
||||||
secureStorageService,
|
|
||||||
stateFactory,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("StateVersion 3 to 4 migration", async () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
const globalVersion3: Partial<GlobalState> = {
|
|
||||||
stateVersion: StateVersion.Three,
|
|
||||||
};
|
|
||||||
|
|
||||||
storageService.get("global", Arg.any()).resolves(globalVersion3);
|
|
||||||
storageService.get("authenticatedAccounts", Arg.any()).resolves([userId]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clears everBeenUnlocked", async () => {
|
|
||||||
const accountVersion3: Account = {
|
|
||||||
profile: {
|
|
||||||
apiKeyClientId: null,
|
|
||||||
convertAccountToKeyConnector: null,
|
|
||||||
email: "EMAIL",
|
|
||||||
emailVerified: true,
|
|
||||||
everBeenUnlocked: true,
|
|
||||||
hasPremiumPersonally: false,
|
|
||||||
kdfIterations: 100000,
|
|
||||||
kdfType: 0,
|
|
||||||
keyHash: "KEY_HASH",
|
|
||||||
lastSync: "LAST_SYNC",
|
|
||||||
userId: userId,
|
|
||||||
usesKeyConnector: false,
|
|
||||||
forcePasswordReset: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const expectedAccountVersion4: Account = {
|
|
||||||
profile: {
|
|
||||||
...accountVersion3.profile,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
delete expectedAccountVersion4.profile.everBeenUnlocked;
|
|
||||||
|
|
||||||
storageService.get(userId, Arg.any()).resolves(accountVersion3);
|
|
||||||
|
|
||||||
await stateMigrationService.migrate();
|
|
||||||
|
|
||||||
storageService.received(1).save(userId, expectedAccountVersion4, Arg.any());
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates StateVersion number", async () => {
|
|
||||||
await stateMigrationService.migrate();
|
|
||||||
|
|
||||||
storageService.received(1).save(
|
|
||||||
"global",
|
|
||||||
Arg.is((globals: GlobalState) => globals.stateVersion === StateVersion.Four),
|
|
||||||
Arg.any(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { webcrypto } from "crypto";
|
|
||||||
|
|
||||||
Object.defineProperty(window, "crypto", {
|
|
||||||
value: webcrypto,
|
|
||||||
});
|
|
||||||
@@ -1,7 +1,3 @@
|
|||||||
import { Substitute, Arg } from "@fluffy-spoon/substitute";
|
|
||||||
|
|
||||||
import { EncString } from "@/jslib/common/src/models/domain/encString";
|
|
||||||
|
|
||||||
function newGuid() {
|
function newGuid() {
|
||||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
||||||
const r = (Math.random() * 16) | 0;
|
const r = (Math.random() * 16) | 0;
|
||||||
@@ -21,17 +17,10 @@ export function BuildTestObject<T, K extends keyof T = keyof T>(
|
|||||||
return Object.assign(constructor === null ? {} : new constructor(), def) as T;
|
return Object.assign(constructor === null ? {} : new constructor(), def) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mockEnc(s: string): EncString {
|
|
||||||
const mock = Substitute.for<EncString>();
|
|
||||||
mock.decrypt(Arg.any(), Arg.any()).resolves(s);
|
|
||||||
|
|
||||||
return mock;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeStaticByteArray(length: number, start = 0) {
|
export function makeStaticByteArray(length: number, start = 0) {
|
||||||
const arr = new Uint8Array(length);
|
const arr = new Uint8Array(length);
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
arr[i] = start + i;
|
arr[i] = start + i;
|
||||||
}
|
}
|
||||||
return arr;
|
return arr.buffer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
import { HashPurpose } from "../enums/hashPurpose";
|
|
||||||
import { KdfType } from "../enums/kdfType";
|
|
||||||
import { KeySuffixOptions } from "../enums/keySuffixOptions";
|
|
||||||
import { EncArrayBuffer } from "../models/domain/encArrayBuffer";
|
|
||||||
import { EncString } from "../models/domain/encString";
|
|
||||||
import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
|
|
||||||
import { ProfileOrganizationResponse } from "../models/response/profileOrganizationResponse";
|
|
||||||
import { ProfileProviderOrganizationResponse } from "../models/response/profileProviderOrganizationResponse";
|
|
||||||
import { ProfileProviderResponse } from "../models/response/profileProviderResponse";
|
|
||||||
|
|
||||||
export abstract class CryptoService {
|
|
||||||
setKey: (key: SymmetricCryptoKey) => Promise<any>;
|
|
||||||
setKeyHash: (keyHash: string) => Promise<void>;
|
|
||||||
setEncKey: (encKey: string) => Promise<void>;
|
|
||||||
setEncPrivateKey: (encPrivateKey: string) => Promise<void>;
|
|
||||||
setOrgKeys: (
|
|
||||||
orgs: ProfileOrganizationResponse[],
|
|
||||||
providerOrgs: ProfileProviderOrganizationResponse[],
|
|
||||||
) => Promise<void>;
|
|
||||||
setProviderKeys: (orgs: ProfileProviderResponse[]) => Promise<void>;
|
|
||||||
getKey: (keySuffix?: KeySuffixOptions, userId?: string) => Promise<SymmetricCryptoKey>;
|
|
||||||
getKeyFromStorage: (keySuffix: KeySuffixOptions, userId?: string) => Promise<SymmetricCryptoKey>;
|
|
||||||
getKeyHash: () => Promise<string>;
|
|
||||||
compareAndUpdateKeyHash: (masterPassword: string, key: SymmetricCryptoKey) => Promise<boolean>;
|
|
||||||
getEncKey: (key?: SymmetricCryptoKey) => Promise<SymmetricCryptoKey>;
|
|
||||||
getPublicKey: () => Promise<ArrayBuffer>;
|
|
||||||
getPrivateKey: () => Promise<ArrayBuffer>;
|
|
||||||
getFingerprint: (userId: string, publicKey?: ArrayBuffer) => Promise<string[]>;
|
|
||||||
getOrgKeys: () => Promise<Map<string, SymmetricCryptoKey>>;
|
|
||||||
getOrgKey: (orgId: string) => Promise<SymmetricCryptoKey>;
|
|
||||||
getProviderKey: (providerId: string) => Promise<SymmetricCryptoKey>;
|
|
||||||
hasKey: () => Promise<boolean>;
|
|
||||||
hasKeyInMemory: (userId?: string) => Promise<boolean>;
|
|
||||||
hasKeyStored: (keySuffix?: KeySuffixOptions, userId?: string) => Promise<boolean>;
|
|
||||||
hasEncKey: () => Promise<boolean>;
|
|
||||||
clearKey: (clearSecretStorage?: boolean, userId?: string) => Promise<any>;
|
|
||||||
clearKeyHash: () => Promise<any>;
|
|
||||||
clearEncKey: (memoryOnly?: boolean, userId?: string) => Promise<any>;
|
|
||||||
clearKeyPair: (memoryOnly?: boolean, userId?: string) => Promise<any>;
|
|
||||||
clearOrgKeys: (memoryOnly?: boolean, userId?: string) => Promise<any>;
|
|
||||||
clearProviderKeys: (memoryOnly?: boolean) => Promise<any>;
|
|
||||||
clearPinProtectedKey: () => Promise<any>;
|
|
||||||
clearKeys: (userId?: string) => Promise<any>;
|
|
||||||
toggleKey: () => Promise<any>;
|
|
||||||
makeKey: (
|
|
||||||
password: string,
|
|
||||||
salt: string,
|
|
||||||
kdf: KdfType,
|
|
||||||
kdfIterations: number,
|
|
||||||
) => Promise<SymmetricCryptoKey>;
|
|
||||||
makeKeyFromPin: (
|
|
||||||
pin: string,
|
|
||||||
salt: string,
|
|
||||||
kdf: KdfType,
|
|
||||||
kdfIterations: number,
|
|
||||||
protectedKeyCs?: EncString,
|
|
||||||
) => Promise<SymmetricCryptoKey>;
|
|
||||||
makeShareKey: () => Promise<[EncString, SymmetricCryptoKey]>;
|
|
||||||
makeKeyPair: (key?: SymmetricCryptoKey) => Promise<[string, EncString]>;
|
|
||||||
makePinKey: (
|
|
||||||
pin: string,
|
|
||||||
salt: string,
|
|
||||||
kdf: KdfType,
|
|
||||||
kdfIterations: number,
|
|
||||||
) => Promise<SymmetricCryptoKey>;
|
|
||||||
makeSendKey: (keyMaterial: ArrayBuffer) => Promise<SymmetricCryptoKey>;
|
|
||||||
hashPassword: (
|
|
||||||
password: string,
|
|
||||||
key: SymmetricCryptoKey,
|
|
||||||
hashPurpose?: HashPurpose,
|
|
||||||
) => Promise<string>;
|
|
||||||
makeEncKey: (key: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, EncString]>;
|
|
||||||
remakeEncKey: (
|
|
||||||
key: SymmetricCryptoKey,
|
|
||||||
encKey?: SymmetricCryptoKey,
|
|
||||||
) => Promise<[SymmetricCryptoKey, EncString]>;
|
|
||||||
encrypt: (plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey) => Promise<EncString>;
|
|
||||||
encryptToBytes: (plainValue: ArrayBuffer, key?: SymmetricCryptoKey) => Promise<EncArrayBuffer>;
|
|
||||||
rsaEncrypt: (data: ArrayBuffer, publicKey?: ArrayBuffer) => Promise<EncString>;
|
|
||||||
rsaDecrypt: (encValue: string, privateKeyValue?: ArrayBuffer) => Promise<ArrayBuffer>;
|
|
||||||
decryptToBytes: (encString: EncString, key?: SymmetricCryptoKey) => Promise<ArrayBuffer>;
|
|
||||||
decryptToUtf8: (encString: EncString, key?: SymmetricCryptoKey) => Promise<string>;
|
|
||||||
decryptFromBytes: (encBuf: ArrayBuffer, key: SymmetricCryptoKey) => Promise<ArrayBuffer>;
|
|
||||||
randomNumber: (min: number, max: number) => Promise<number>;
|
|
||||||
validateKey: (key: SymmetricCryptoKey) => Promise<boolean>;
|
|
||||||
}
|
|
||||||
@@ -1,34 +1,2 @@
|
|||||||
import { Observable } from "rxjs";
|
// Stub file - re-exports DC EnvironmentService
|
||||||
|
export { EnvironmentService, EnvironmentUrls } from "@/libs/abstractions/environment.service";
|
||||||
export type Urls = {
|
|
||||||
base?: string;
|
|
||||||
webVault?: string;
|
|
||||||
api?: string;
|
|
||||||
identity?: string;
|
|
||||||
icons?: string;
|
|
||||||
notifications?: string;
|
|
||||||
events?: string;
|
|
||||||
keyConnector?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PayPalConfig = {
|
|
||||||
businessId?: string;
|
|
||||||
buttonAction?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export abstract class EnvironmentService {
|
|
||||||
urls: Observable<Urls>;
|
|
||||||
|
|
||||||
hasBaseUrl: () => boolean;
|
|
||||||
getNotificationsUrl: () => string;
|
|
||||||
getWebVaultUrl: () => string;
|
|
||||||
getSendUrl: () => string;
|
|
||||||
getIconsUrl: () => string;
|
|
||||||
getApiUrl: () => string;
|
|
||||||
getIdentityUrl: () => string;
|
|
||||||
getEventsUrl: () => string;
|
|
||||||
getKeyConnectorUrl: () => string;
|
|
||||||
setUrlsFromStorage: () => Promise<void>;
|
|
||||||
setUrls: (urls: Urls) => Promise<Urls>;
|
|
||||||
getUrls: () => Urls;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,218 +1,2 @@
|
|||||||
import { Observable } from "rxjs";
|
// Stub file - re-exports DC StateService
|
||||||
|
export { StateService } from "@/libs/abstractions/state.service";
|
||||||
import { KdfType } from "../enums/kdfType";
|
|
||||||
import { ThemeType } from "../enums/themeType";
|
|
||||||
import { UriMatchType } from "../enums/uriMatchType";
|
|
||||||
import { OrganizationData } from "../models/data/organizationData";
|
|
||||||
import { ProviderData } from "../models/data/providerData";
|
|
||||||
import { Account } from "../models/domain/account";
|
|
||||||
import { EncString } from "../models/domain/encString";
|
|
||||||
import { EnvironmentUrls } from "../models/domain/environmentUrls";
|
|
||||||
import { StorageOptions } from "../models/domain/storageOptions";
|
|
||||||
import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
|
|
||||||
import { WindowState } from "../models/domain/windowState";
|
|
||||||
|
|
||||||
export abstract class StateService<T extends Account = Account> {
|
|
||||||
accounts$: Observable<{ [userId: string]: T }>;
|
|
||||||
activeAccount$: Observable<string>;
|
|
||||||
|
|
||||||
addAccount: (account: T) => Promise<void>;
|
|
||||||
setActiveUser: (userId: string) => Promise<void>;
|
|
||||||
clean: (options?: StorageOptions) => Promise<void>;
|
|
||||||
init: () => Promise<void>;
|
|
||||||
|
|
||||||
getAccessToken: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setAccessToken: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getAddEditCipherInfo: (options?: StorageOptions) => Promise<any>;
|
|
||||||
setAddEditCipherInfo: (value: any, options?: StorageOptions) => Promise<void>;
|
|
||||||
getAlwaysShowDock: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setAlwaysShowDock: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getApiKeyClientId: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setApiKeyClientId: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getApiKeyClientSecret: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setApiKeyClientSecret: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getAutoConfirmFingerPrints: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setAutoConfirmFingerprints: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getBiometricAwaitingAcceptance: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setBiometricAwaitingAcceptance: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getBiometricFingerprintValidated: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setBiometricFingerprintValidated: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getBiometricLocked: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setBiometricLocked: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getBiometricText: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setBiometricText: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getBiometricUnlock: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setBiometricUnlock: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getCanAccessPremium: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
getClearClipboard: (options?: StorageOptions) => Promise<number>;
|
|
||||||
setClearClipboard: (value: number, options?: StorageOptions) => Promise<void>;
|
|
||||||
getCollapsedGroupings: (options?: StorageOptions) => Promise<string[]>;
|
|
||||||
setCollapsedGroupings: (value: string[], options?: StorageOptions) => Promise<void>;
|
|
||||||
getConvertAccountToKeyConnector: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setConvertAccountToKeyConnector: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getCryptoMasterKey: (options?: StorageOptions) => Promise<SymmetricCryptoKey>;
|
|
||||||
setCryptoMasterKey: (value: SymmetricCryptoKey, options?: StorageOptions) => Promise<void>;
|
|
||||||
getCryptoMasterKeyAuto: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setCryptoMasterKeyAuto: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getCryptoMasterKeyB64: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setCryptoMasterKeyB64: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getCryptoMasterKeyBiometric: (options?: StorageOptions) => Promise<string>;
|
|
||||||
hasCryptoMasterKeyBiometric: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setCryptoMasterKeyBiometric: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getDecodedToken: (options?: StorageOptions) => Promise<any>;
|
|
||||||
setDecodedToken: (value: any, options?: StorageOptions) => Promise<void>;
|
|
||||||
getDecryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise<SymmetricCryptoKey>;
|
|
||||||
setDecryptedCryptoSymmetricKey: (
|
|
||||||
value: SymmetricCryptoKey,
|
|
||||||
options?: StorageOptions,
|
|
||||||
) => Promise<void>;
|
|
||||||
getDecryptedOrganizationKeys: (
|
|
||||||
options?: StorageOptions,
|
|
||||||
) => Promise<Map<string, SymmetricCryptoKey>>;
|
|
||||||
setDecryptedOrganizationKeys: (
|
|
||||||
value: Map<string, SymmetricCryptoKey>,
|
|
||||||
options?: StorageOptions,
|
|
||||||
) => Promise<void>;
|
|
||||||
getDecryptedPinProtected: (options?: StorageOptions) => Promise<EncString>;
|
|
||||||
setDecryptedPinProtected: (value: EncString, options?: StorageOptions) => Promise<void>;
|
|
||||||
getDecryptedPrivateKey: (options?: StorageOptions) => Promise<ArrayBuffer>;
|
|
||||||
setDecryptedPrivateKey: (value: ArrayBuffer, options?: StorageOptions) => Promise<void>;
|
|
||||||
getDecryptedProviderKeys: (options?: StorageOptions) => Promise<Map<string, SymmetricCryptoKey>>;
|
|
||||||
setDecryptedProviderKeys: (
|
|
||||||
value: Map<string, SymmetricCryptoKey>,
|
|
||||||
options?: StorageOptions,
|
|
||||||
) => Promise<void>;
|
|
||||||
getDefaultUriMatch: (options?: StorageOptions) => Promise<UriMatchType>;
|
|
||||||
setDefaultUriMatch: (value: UriMatchType, options?: StorageOptions) => Promise<void>;
|
|
||||||
getDisableAutoBiometricsPrompt: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setDisableAutoBiometricsPrompt: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getDisableAutoTotpCopy: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setDisableAutoTotpCopy: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getDisableBadgeCounter: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setDisableBadgeCounter: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getDisableContextMenuItem: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setDisableContextMenuItem: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getDisableGa: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setDisableGa: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getEmail: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setEmail: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getEmailVerified: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setEmailVerified: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getEnableAlwaysOnTop: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setEnableAlwaysOnTop: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getEnableBiometric: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setEnableBiometric: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getEnableCloseToTray: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setEnableCloseToTray: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getEnableFullWidth: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setEnableFullWidth: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getEnableMinimizeToTray: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setEnableMinimizeToTray: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getEnableStartToTray: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setEnableStartToTray: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getEnableTray: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setEnableTray: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getEncryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setEncryptedCryptoSymmetricKey: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getEncryptedOrganizationKeys: (options?: StorageOptions) => Promise<any>;
|
|
||||||
setEncryptedOrganizationKeys: (
|
|
||||||
value: Map<string, SymmetricCryptoKey>,
|
|
||||||
options?: StorageOptions,
|
|
||||||
) => Promise<void>;
|
|
||||||
getEncryptedPinProtected: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setEncryptedPinProtected: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getEncryptedPrivateKey: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setEncryptedPrivateKey: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getEncryptedProviderKeys: (options?: StorageOptions) => Promise<any>;
|
|
||||||
setEncryptedProviderKeys: (value: any, options?: StorageOptions) => Promise<void>;
|
|
||||||
getEntityId: (options?: StorageOptions) => Promise<string>;
|
|
||||||
getEnvironmentUrls: (options?: StorageOptions) => Promise<EnvironmentUrls>;
|
|
||||||
setEnvironmentUrls: (value: EnvironmentUrls, options?: StorageOptions) => Promise<void>;
|
|
||||||
getEquivalentDomains: (options?: StorageOptions) => Promise<any>;
|
|
||||||
setEquivalentDomains: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getEverBeenUnlocked: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setEverBeenUnlocked: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getForcePasswordReset: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setForcePasswordReset: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getInstalledVersion: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setInstalledVersion: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getIsAuthenticated: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
getKdfIterations: (options?: StorageOptions) => Promise<number>;
|
|
||||||
setKdfIterations: (value: number, options?: StorageOptions) => Promise<void>;
|
|
||||||
getKdfType: (options?: StorageOptions) => Promise<KdfType>;
|
|
||||||
setKdfType: (value: KdfType, options?: StorageOptions) => Promise<void>;
|
|
||||||
getKeyHash: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setKeyHash: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getLastActive: (options?: StorageOptions) => Promise<number>;
|
|
||||||
setLastActive: (value: number, options?: StorageOptions) => Promise<void>;
|
|
||||||
getLastSync: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setLastSync: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getLegacyEtmKey: (options?: StorageOptions) => Promise<SymmetricCryptoKey>;
|
|
||||||
setLegacyEtmKey: (value: SymmetricCryptoKey, options?: StorageOptions) => Promise<void>;
|
|
||||||
getLocalData: (options?: StorageOptions) => Promise<any>;
|
|
||||||
setLocalData: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getLocale: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setLocale: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getLoginRedirect: (options?: StorageOptions) => Promise<any>;
|
|
||||||
setLoginRedirect: (value: any, options?: StorageOptions) => Promise<void>;
|
|
||||||
getMainWindowSize: (options?: StorageOptions) => Promise<number>;
|
|
||||||
setMainWindowSize: (value: number, options?: StorageOptions) => Promise<void>;
|
|
||||||
getMinimizeOnCopyToClipboard: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setMinimizeOnCopyToClipboard: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getNeverDomains: (options?: StorageOptions) => Promise<{ [id: string]: any }>;
|
|
||||||
setNeverDomains: (value: { [id: string]: any }, options?: StorageOptions) => Promise<void>;
|
|
||||||
getNoAutoPromptBiometrics: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setNoAutoPromptBiometrics: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getNoAutoPromptBiometricsText: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setNoAutoPromptBiometricsText: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getOpenAtLogin: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setOpenAtLogin: (value: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getOrganizationInvitation: (options?: StorageOptions) => Promise<any>;
|
|
||||||
setOrganizationInvitation: (value: any, options?: StorageOptions) => Promise<void>;
|
|
||||||
getOrganizations: (options?: StorageOptions) => Promise<{ [id: string]: OrganizationData }>;
|
|
||||||
setOrganizations: (
|
|
||||||
value: { [id: string]: OrganizationData },
|
|
||||||
options?: StorageOptions,
|
|
||||||
) => Promise<void>;
|
|
||||||
getPasswordGenerationOptions: (options?: StorageOptions) => Promise<any>;
|
|
||||||
setPasswordGenerationOptions: (value: any, options?: StorageOptions) => Promise<void>;
|
|
||||||
getUsernameGenerationOptions: (options?: StorageOptions) => Promise<any>;
|
|
||||||
setUsernameGenerationOptions: (value: any, options?: StorageOptions) => Promise<void>;
|
|
||||||
getGeneratorOptions: (options?: StorageOptions) => Promise<any>;
|
|
||||||
setGeneratorOptions: (value: any, options?: StorageOptions) => Promise<void>;
|
|
||||||
getProtectedPin: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setProtectedPin: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getProviders: (options?: StorageOptions) => Promise<{ [id: string]: ProviderData }>;
|
|
||||||
setProviders: (value: { [id: string]: ProviderData }, options?: StorageOptions) => Promise<void>;
|
|
||||||
getPublicKey: (options?: StorageOptions) => Promise<ArrayBuffer>;
|
|
||||||
setPublicKey: (value: ArrayBuffer, options?: StorageOptions) => Promise<void>;
|
|
||||||
getRefreshToken: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setRefreshToken: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getRememberedEmail: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setRememberedEmail: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getSecurityStamp: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setSecurityStamp: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getSettings: (options?: StorageOptions) => Promise<any>;
|
|
||||||
setSettings: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getSsoCodeVerifier: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setSsoCodeVerifier: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getSsoOrgIdentifier: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setSsoOrganizationIdentifier: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getSsoState: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setSsoState: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getTheme: (options?: StorageOptions) => Promise<ThemeType>;
|
|
||||||
setTheme: (value: ThemeType, options?: StorageOptions) => Promise<void>;
|
|
||||||
getTwoFactorToken: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setTwoFactorToken: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getUserId: (options?: StorageOptions) => Promise<string>;
|
|
||||||
getUsesKeyConnector: (options?: StorageOptions) => Promise<boolean>;
|
|
||||||
setUsesKeyConnector: (vaule: boolean, options?: StorageOptions) => Promise<void>;
|
|
||||||
getVaultTimeout: (options?: StorageOptions) => Promise<number>;
|
|
||||||
setVaultTimeout: (value: number, options?: StorageOptions) => Promise<void>;
|
|
||||||
getVaultTimeoutAction: (options?: StorageOptions) => Promise<string>;
|
|
||||||
setVaultTimeoutAction: (value: string, options?: StorageOptions) => Promise<void>;
|
|
||||||
getStateVersion: () => Promise<number>;
|
|
||||||
setStateVersion: (value: number) => Promise<void>;
|
|
||||||
getWindow: () => Promise<WindowState>;
|
|
||||||
setWindow: (value: WindowState) => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
export abstract class StateMigrationService {
|
|
||||||
needsMigration: () => Promise<boolean>;
|
|
||||||
migrate: () => Promise<void>;
|
|
||||||
}
|
|
||||||
@@ -1,32 +1,2 @@
|
|||||||
import { IdentityTokenResponse } from "../models/response/identityTokenResponse";
|
// Stub file - re-exports DC TokenService
|
||||||
|
export { TokenService } from "@/libs/abstractions/token.service";
|
||||||
export abstract class TokenService {
|
|
||||||
setTokens: (
|
|
||||||
accessToken: string,
|
|
||||||
refreshToken: string,
|
|
||||||
clientIdClientSecret: [string, string],
|
|
||||||
) => Promise<any>;
|
|
||||||
setToken: (token: string) => Promise<any>;
|
|
||||||
getToken: () => Promise<string>;
|
|
||||||
setRefreshToken: (refreshToken: string) => Promise<any>;
|
|
||||||
getRefreshToken: () => Promise<string>;
|
|
||||||
setClientId: (clientId: string) => Promise<any>;
|
|
||||||
getClientId: () => Promise<string>;
|
|
||||||
setClientSecret: (clientSecret: string) => Promise<any>;
|
|
||||||
getClientSecret: () => Promise<string>;
|
|
||||||
setTwoFactorToken: (tokenResponse: IdentityTokenResponse) => Promise<any>;
|
|
||||||
getTwoFactorToken: () => Promise<string>;
|
|
||||||
clearTwoFactorToken: () => Promise<any>;
|
|
||||||
clearToken: (userId?: string) => Promise<any>;
|
|
||||||
decodeToken: (token?: string) => any;
|
|
||||||
getTokenExpirationDate: () => Promise<Date>;
|
|
||||||
tokenSecondsRemaining: (offsetSeconds?: number) => Promise<number>;
|
|
||||||
tokenNeedsRefresh: (minutes?: number) => Promise<boolean>;
|
|
||||||
getUserId: () => Promise<string>;
|
|
||||||
getEmail: () => Promise<string>;
|
|
||||||
getEmailVerified: () => Promise<boolean>;
|
|
||||||
getName: () => Promise<string>;
|
|
||||||
getPremium: () => Promise<boolean>;
|
|
||||||
getIssuer: () => Promise<string>;
|
|
||||||
getIsExternal: () => Promise<boolean>;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
export enum AuthenticationStatus {
|
|
||||||
Locked = "locked",
|
|
||||||
Unlocked = "unlocked",
|
|
||||||
LoggedOut = "loggedOut",
|
|
||||||
Active = "active",
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export enum CipherRepromptType {
|
|
||||||
None = 0,
|
|
||||||
Password = 1,
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
export enum CipherType {
|
|
||||||
Login = 1,
|
|
||||||
SecureNote = 2,
|
|
||||||
Card = 3,
|
|
||||||
Identity = 4,
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export enum EmergencyAccessStatusType {
|
|
||||||
Invited = 0,
|
|
||||||
Accepted = 1,
|
|
||||||
Confirmed = 2,
|
|
||||||
RecoveryInitiated = 3,
|
|
||||||
RecoveryApproved = 4,
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export enum EmergencyAccessType {
|
|
||||||
View = 0,
|
|
||||||
Takeover = 1,
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
export enum FieldType {
|
|
||||||
Text = 0,
|
|
||||||
Hidden = 1,
|
|
||||||
Boolean = 2,
|
|
||||||
Linked = 3,
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export enum FileUploadType {
|
|
||||||
Direct = 0,
|
|
||||||
Azure = 1,
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export enum HashPurpose {
|
|
||||||
ServerAuthorization = 1,
|
|
||||||
LocalAuthorization = 2,
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export enum KeySuffixOptions {
|
|
||||||
Auto = "auto",
|
|
||||||
Biometric = "biometric",
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
export enum NotificationType {
|
|
||||||
SyncCipherUpdate = 0,
|
|
||||||
SyncCipherCreate = 1,
|
|
||||||
SyncLoginDelete = 2,
|
|
||||||
SyncFolderDelete = 3,
|
|
||||||
SyncCiphers = 4,
|
|
||||||
|
|
||||||
SyncVault = 5,
|
|
||||||
SyncOrgKeys = 6,
|
|
||||||
SyncFolderCreate = 7,
|
|
||||||
SyncFolderUpdate = 8,
|
|
||||||
SyncCipherDelete = 9,
|
|
||||||
SyncSettings = 10,
|
|
||||||
|
|
||||||
LogOut = 11,
|
|
||||||
|
|
||||||
SyncSendCreate = 12,
|
|
||||||
SyncSendUpdate = 13,
|
|
||||||
SyncSendDelete = 14,
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export enum OrganizationUserStatusType {
|
|
||||||
Invited = 0,
|
|
||||||
Accepted = 1,
|
|
||||||
Confirmed = 2,
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export enum OrganizationUserType {
|
|
||||||
Owner = 0,
|
|
||||||
Admin = 1,
|
|
||||||
User = 2,
|
|
||||||
Manager = 3,
|
|
||||||
Custom = 4,
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
export enum PaymentMethodType {
|
|
||||||
Card = 0,
|
|
||||||
BankAccount = 1,
|
|
||||||
PayPal = 2,
|
|
||||||
BitPay = 3,
|
|
||||||
Credit = 4,
|
|
||||||
WireTransfer = 5,
|
|
||||||
AppleInApp = 6,
|
|
||||||
GoogleInApp = 7,
|
|
||||||
Check = 8,
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
export enum Permissions {
|
|
||||||
AccessEventLogs,
|
|
||||||
AccessImportExport,
|
|
||||||
AccessReports,
|
|
||||||
/**
|
|
||||||
* @deprecated Sep 29 2021: This permission has been split out to `createNewCollections`, `editAnyCollection`, and
|
|
||||||
* `deleteAnyCollection`. It exists here for backwards compatibility with Server versions <= 1.43.0
|
|
||||||
*/
|
|
||||||
ManageAllCollections,
|
|
||||||
/**
|
|
||||||
* @deprecated Sep 29 2021: This permission has been split out to `editAssignedCollections` and
|
|
||||||
* `deleteAssignedCollections`. It exists here for backwards compatibility with Server versions <= 1.43.0
|
|
||||||
*/
|
|
||||||
ManageAssignedCollections,
|
|
||||||
ManageGroups,
|
|
||||||
ManageOrganization,
|
|
||||||
ManagePolicies,
|
|
||||||
ManageProvider,
|
|
||||||
ManageUsers,
|
|
||||||
ManageUsersPassword,
|
|
||||||
CreateNewCollections,
|
|
||||||
EditAnyCollection,
|
|
||||||
DeleteAnyCollection,
|
|
||||||
EditAssignedCollections,
|
|
||||||
DeleteAssignedCollections,
|
|
||||||
ManageSso,
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export enum PlanSponsorshipType {
|
|
||||||
FamiliesForEnterprise = 0,
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
export enum PlanType {
|
|
||||||
Free = 0,
|
|
||||||
FamiliesAnnually2019 = 1,
|
|
||||||
TeamsMonthly2019 = 2,
|
|
||||||
TeamsAnnually2019 = 3,
|
|
||||||
EnterpriseMonthly2019 = 4,
|
|
||||||
EnterpriseAnnually2019 = 5,
|
|
||||||
Custom = 6,
|
|
||||||
FamiliesAnnually = 7,
|
|
||||||
TeamsMonthly = 8,
|
|
||||||
TeamsAnnually = 9,
|
|
||||||
EnterpriseMonthly = 10,
|
|
||||||
EnterpriseAnnually = 11,
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
export enum ProductType {
|
|
||||||
Free = 0,
|
|
||||||
Families = 1,
|
|
||||||
Teams = 2,
|
|
||||||
Enterprise = 3,
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export enum ProviderUserStatusType {
|
|
||||||
Invited = 0,
|
|
||||||
Accepted = 1,
|
|
||||||
Confirmed = 2,
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export enum ProviderUserType {
|
|
||||||
ProviderAdmin = 0,
|
|
||||||
ServiceUser = 1,
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
export enum SsoType {
|
|
||||||
None = 0,
|
|
||||||
OpenIdConnect = 1,
|
|
||||||
Saml2 = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum OpenIdConnectRedirectBehavior {
|
|
||||||
RedirectGet = 0,
|
|
||||||
FormPost = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum Saml2BindingType {
|
|
||||||
HttpRedirect = 1,
|
|
||||||
HttpPost = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum Saml2NameIdFormat {
|
|
||||||
NotConfigured = 0,
|
|
||||||
Unspecified = 1,
|
|
||||||
EmailAddress = 2,
|
|
||||||
X509SubjectName = 3,
|
|
||||||
WindowsDomainQualifiedName = 4,
|
|
||||||
KerberosPrincipalName = 5,
|
|
||||||
EntityIdentifier = 6,
|
|
||||||
Persistent = 7,
|
|
||||||
Transient = 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum Saml2SigningBehavior {
|
|
||||||
IfIdpWantAuthnRequestsSigned = 0,
|
|
||||||
Always = 1,
|
|
||||||
Never = 3,
|
|
||||||
}
|
|
||||||
@@ -3,5 +3,6 @@ export enum StateVersion {
|
|||||||
Two = 2, // Move to a typed State object
|
Two = 2, // Move to a typed State object
|
||||||
Three = 3, // Fix migration of users' premium status
|
Three = 3, // Fix migration of users' premium status
|
||||||
Four = 4, // Fix 'Never Lock' option by removing stale data
|
Four = 4, // Fix 'Never Lock' option by removing stale data
|
||||||
Latest = Four,
|
Five = 5, // New state service implementation
|
||||||
|
Latest = Five,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
export enum TransactionType {
|
|
||||||
Charge = 0,
|
|
||||||
Credit = 1,
|
|
||||||
PromotionalCredit = 2,
|
|
||||||
ReferralCredit = 3,
|
|
||||||
Refund = 4,
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
export enum UriMatchType {
|
|
||||||
Domain = 0,
|
|
||||||
Host = 1,
|
|
||||||
StartsWith = 2,
|
|
||||||
Exact = 3,
|
|
||||||
RegularExpression = 4,
|
|
||||||
Never = 5,
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { Account } from "../models/domain/account";
|
|
||||||
|
|
||||||
export class AccountFactory<T extends Account = Account> {
|
|
||||||
private accountConstructor: new (init: Partial<T>) => T;
|
|
||||||
|
|
||||||
constructor(accountConstructor: new (init: Partial<T>) => T) {
|
|
||||||
this.accountConstructor = accountConstructor;
|
|
||||||
}
|
|
||||||
|
|
||||||
create(args: Partial<T>) {
|
|
||||||
return new this.accountConstructor(args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { GlobalState } from "../models/domain/globalState";
|
|
||||||
|
|
||||||
export class GlobalStateFactory<T extends GlobalState = GlobalState> {
|
|
||||||
private globalStateConstructor: new (init: Partial<T>) => T;
|
|
||||||
|
|
||||||
constructor(globalStateConstructor: new (init: Partial<T>) => T) {
|
|
||||||
this.globalStateConstructor = globalStateConstructor;
|
|
||||||
}
|
|
||||||
|
|
||||||
create(args?: Partial<T>) {
|
|
||||||
return new this.globalStateConstructor(args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { Account } from "../models/domain/account";
|
|
||||||
import { GlobalState } from "../models/domain/globalState";
|
|
||||||
|
|
||||||
import { AccountFactory } from "./accountFactory";
|
|
||||||
import { GlobalStateFactory } from "./globalStateFactory";
|
|
||||||
|
|
||||||
export class StateFactory<
|
|
||||||
TGlobal extends GlobalState = GlobalState,
|
|
||||||
TAccount extends Account = Account,
|
|
||||||
> {
|
|
||||||
private globalStateFactory: GlobalStateFactory<TGlobal>;
|
|
||||||
private accountFactory: AccountFactory<TAccount>;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
globalStateConstructor: new (init: Partial<TGlobal>) => TGlobal,
|
|
||||||
accountConstructor: new (init: Partial<TAccount>) => TAccount,
|
|
||||||
) {
|
|
||||||
this.globalStateFactory = new GlobalStateFactory(globalStateConstructor);
|
|
||||||
this.accountFactory = new AccountFactory(accountConstructor);
|
|
||||||
}
|
|
||||||
|
|
||||||
createGlobal(args: Partial<TGlobal>): TGlobal {
|
|
||||||
return this.globalStateFactory.create(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
createAccount(args: Partial<TAccount>): TAccount {
|
|
||||||
return this.accountFactory.create(args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -26,9 +26,4 @@ export class NodeUtils {
|
|||||||
.on("error", (err) => reject(err));
|
.on("error", (err) => reject(err));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://stackoverflow.com/a/31394257
|
|
||||||
static bufferToArrayBuffer(buf: Buffer): ArrayBuffer {
|
|
||||||
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
import { ITreeNodeObject, TreeNode } from "../models/domain/treeNode";
|
|
||||||
|
|
||||||
export class ServiceUtils {
|
|
||||||
static nestedTraverse(
|
|
||||||
nodeTree: TreeNode<ITreeNodeObject>[],
|
|
||||||
partIndex: number,
|
|
||||||
parts: string[],
|
|
||||||
obj: ITreeNodeObject,
|
|
||||||
parent: ITreeNodeObject,
|
|
||||||
delimiter: string,
|
|
||||||
) {
|
|
||||||
if (parts.length <= partIndex) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const end = partIndex === parts.length - 1;
|
|
||||||
const partName = parts[partIndex];
|
|
||||||
|
|
||||||
for (let i = 0; i < nodeTree.length; i++) {
|
|
||||||
if (nodeTree[i].node.name !== parts[partIndex]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (end && nodeTree[i].node.id !== obj.id) {
|
|
||||||
// Another node with the same name.
|
|
||||||
nodeTree.push(new TreeNode(obj, partName, parent));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ServiceUtils.nestedTraverse(
|
|
||||||
nodeTree[i].children,
|
|
||||||
partIndex + 1,
|
|
||||||
parts,
|
|
||||||
obj,
|
|
||||||
nodeTree[i].node,
|
|
||||||
delimiter,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nodeTree.filter((n) => n.node.name === partName).length === 0) {
|
|
||||||
if (end) {
|
|
||||||
nodeTree.push(new TreeNode(obj, partName, parent));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const newPartName = parts[partIndex] + delimiter + parts[partIndex + 1];
|
|
||||||
ServiceUtils.nestedTraverse(
|
|
||||||
nodeTree,
|
|
||||||
0,
|
|
||||||
[newPartName, ...parts.slice(partIndex + 2)],
|
|
||||||
obj,
|
|
||||||
parent,
|
|
||||||
delimiter,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static getTreeNodeObject(
|
|
||||||
nodeTree: TreeNode<ITreeNodeObject>[],
|
|
||||||
id: string,
|
|
||||||
): TreeNode<ITreeNodeObject> {
|
|
||||||
for (let i = 0; i < nodeTree.length; i++) {
|
|
||||||
if (nodeTree[i].node.id === id) {
|
|
||||||
return nodeTree[i];
|
|
||||||
} else if (nodeTree[i].children != null) {
|
|
||||||
const node = ServiceUtils.getTreeNodeObject(nodeTree[i].children, id);
|
|
||||||
if (node !== null) {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
/**
|
|
||||||
* Use as a Decorator on async functions, it will limit how many times the function can be
|
|
||||||
* in-flight at a time.
|
|
||||||
*
|
|
||||||
* Calls beyond the limit will be queued, and run when one of the active calls finishes
|
|
||||||
*/
|
|
||||||
export function throttle(limit: number, throttleKey: (args: any[]) => string) {
|
|
||||||
return <T>(
|
|
||||||
target: any,
|
|
||||||
propertyKey: string | symbol,
|
|
||||||
descriptor: TypedPropertyDescriptor<(...args: any[]) => Promise<T>>,
|
|
||||||
) => {
|
|
||||||
const originalMethod: () => Promise<T> = descriptor.value;
|
|
||||||
const allThrottles = new Map<any, Map<string, (() => void)[]>>();
|
|
||||||
|
|
||||||
const getThrottles = (obj: any) => {
|
|
||||||
let throttles = allThrottles.get(obj);
|
|
||||||
if (throttles != null) {
|
|
||||||
return throttles;
|
|
||||||
}
|
|
||||||
throttles = new Map<string, (() => void)[]>();
|
|
||||||
allThrottles.set(obj, throttles);
|
|
||||||
return throttles;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
value: function (...args: any[]) {
|
|
||||||
const throttles = getThrottles(this);
|
|
||||||
const argsThrottleKey = throttleKey(args);
|
|
||||||
let queue = throttles.get(argsThrottleKey);
|
|
||||||
if (queue == null) {
|
|
||||||
queue = [];
|
|
||||||
throttles.set(argsThrottleKey, queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise<T>((resolve, reject) => {
|
|
||||||
const exec = () => {
|
|
||||||
const onFinally = () => {
|
|
||||||
queue.splice(queue.indexOf(exec), 1);
|
|
||||||
if (queue.length >= limit) {
|
|
||||||
queue[limit - 1]();
|
|
||||||
} else if (queue.length === 0) {
|
|
||||||
throttles.delete(argsThrottleKey);
|
|
||||||
if (throttles.size === 0) {
|
|
||||||
allThrottles.delete(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
originalMethod
|
|
||||||
.apply(this, args)
|
|
||||||
.then((val: any) => {
|
|
||||||
onFinally();
|
|
||||||
return val;
|
|
||||||
})
|
|
||||||
.catch((err: any) => {
|
|
||||||
onFinally();
|
|
||||||
throw err;
|
|
||||||
})
|
|
||||||
.then(resolve, reject);
|
|
||||||
};
|
|
||||||
queue.push(exec);
|
|
||||||
if (queue.length <= limit) {
|
|
||||||
exec();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -36,7 +36,7 @@ export class Utils {
|
|||||||
Utils.global = Utils.isNode && !Utils.isBrowser ? global : window;
|
Utils.global = Utils.isNode && !Utils.isBrowser ? global : window;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromB64ToArray(str: string): Uint8Array {
|
static fromB64ToArray(str: string): Uint8Array<ArrayBuffer> {
|
||||||
if (Utils.isNode) {
|
if (Utils.isNode) {
|
||||||
return new Uint8Array(Buffer.from(str, "base64"));
|
return new Uint8Array(Buffer.from(str, "base64"));
|
||||||
} else {
|
} else {
|
||||||
@@ -49,11 +49,11 @@ export class Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromUrlB64ToArray(str: string): Uint8Array {
|
static fromUrlB64ToArray(str: string): Uint8Array<ArrayBuffer> {
|
||||||
return Utils.fromB64ToArray(Utils.fromUrlB64ToB64(str));
|
return Utils.fromB64ToArray(Utils.fromUrlB64ToB64(str));
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromHexToArray(str: string): Uint8Array {
|
static fromHexToArray(str: string): Uint8Array<ArrayBuffer> {
|
||||||
if (Utils.isNode) {
|
if (Utils.isNode) {
|
||||||
return new Uint8Array(Buffer.from(str, "hex"));
|
return new Uint8Array(Buffer.from(str, "hex"));
|
||||||
} else {
|
} else {
|
||||||
@@ -65,7 +65,7 @@ export class Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromUtf8ToArray(str: string): Uint8Array {
|
static fromUtf8ToArray(str: string): Uint8Array<ArrayBuffer> {
|
||||||
if (Utils.isNode) {
|
if (Utils.isNode) {
|
||||||
return new Uint8Array(Buffer.from(str, "utf8"));
|
return new Uint8Array(Buffer.from(str, "utf8"));
|
||||||
} else {
|
} else {
|
||||||
@@ -78,7 +78,7 @@ export class Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromByteStringToArray(str: string): Uint8Array {
|
static fromByteStringToArray(str: string): Uint8Array<ArrayBuffer> {
|
||||||
const arr = new Uint8Array(str.length);
|
const arr = new Uint8Array(str.length);
|
||||||
for (let i = 0; i < str.length; i++) {
|
for (let i = 0; i < str.length; i++) {
|
||||||
arr[i] = str.charCodeAt(i);
|
arr[i] = str.charCodeAt(i);
|
||||||
@@ -99,8 +99,8 @@ export class Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromBufferToUrlB64(buffer: ArrayBuffer): string {
|
static fromBufferToUrlB64(buffer: Uint8Array<ArrayBuffer>): string {
|
||||||
return Utils.fromB64toUrlB64(Utils.fromBufferToB64(buffer));
|
return Utils.fromB64toUrlB64(Utils.fromBufferToB64(buffer.buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromB64toUrlB64(b64Str: string) {
|
static fromB64toUrlB64(b64Str: string) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,55 +0,0 @@
|
|||||||
import { BaseResponse } from "../response/baseResponse";
|
|
||||||
|
|
||||||
export class PermissionsApi extends BaseResponse {
|
|
||||||
accessEventLogs: boolean;
|
|
||||||
accessImportExport: boolean;
|
|
||||||
accessReports: boolean;
|
|
||||||
/**
|
|
||||||
* @deprecated Sep 29 2021: This permission has been split out to `createNewCollections`, `editAnyCollection`, and
|
|
||||||
* `deleteAnyCollection`. It exists here for backwards compatibility with Server versions <= 1.43.0
|
|
||||||
*/
|
|
||||||
manageAllCollections: boolean;
|
|
||||||
createNewCollections: boolean;
|
|
||||||
editAnyCollection: boolean;
|
|
||||||
deleteAnyCollection: boolean;
|
|
||||||
/**
|
|
||||||
* @deprecated Sep 29 2021: This permission has been split out to `editAssignedCollections` and
|
|
||||||
* `deleteAssignedCollections`. It exists here for backwards compatibility with Server versions <= 1.43.0
|
|
||||||
*/
|
|
||||||
manageAssignedCollections: boolean;
|
|
||||||
editAssignedCollections: boolean;
|
|
||||||
deleteAssignedCollections: boolean;
|
|
||||||
manageCiphers: boolean;
|
|
||||||
manageGroups: boolean;
|
|
||||||
manageSso: boolean;
|
|
||||||
managePolicies: boolean;
|
|
||||||
manageUsers: boolean;
|
|
||||||
manageResetPassword: boolean;
|
|
||||||
|
|
||||||
constructor(data: any = null) {
|
|
||||||
super(data);
|
|
||||||
if (data == null) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
this.accessEventLogs = this.getResponseProperty("AccessEventLogs");
|
|
||||||
this.accessImportExport = this.getResponseProperty("AccessImportExport");
|
|
||||||
this.accessReports = this.getResponseProperty("AccessReports");
|
|
||||||
|
|
||||||
// For backwards compatibility with Server <= 1.43.0
|
|
||||||
this.manageAllCollections = this.getResponseProperty("ManageAllCollections");
|
|
||||||
this.manageAssignedCollections = this.getResponseProperty("ManageAssignedCollections");
|
|
||||||
|
|
||||||
this.createNewCollections = this.getResponseProperty("CreateNewCollections");
|
|
||||||
this.editAnyCollection = this.getResponseProperty("EditAnyCollection");
|
|
||||||
this.deleteAnyCollection = this.getResponseProperty("DeleteAnyCollection");
|
|
||||||
this.editAssignedCollections = this.getResponseProperty("EditAssignedCollections");
|
|
||||||
this.deleteAssignedCollections = this.getResponseProperty("DeleteAssignedCollections");
|
|
||||||
|
|
||||||
this.manageCiphers = this.getResponseProperty("ManageCiphers");
|
|
||||||
this.manageGroups = this.getResponseProperty("ManageGroups");
|
|
||||||
this.manageSso = this.getResponseProperty("ManageSso");
|
|
||||||
this.managePolicies = this.getResponseProperty("ManagePolicies");
|
|
||||||
this.manageUsers = this.getResponseProperty("ManageUsers");
|
|
||||||
this.manageResetPassword = this.getResponseProperty("ManageResetPassword");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
import {
|
|
||||||
OpenIdConnectRedirectBehavior,
|
|
||||||
Saml2BindingType,
|
|
||||||
Saml2NameIdFormat,
|
|
||||||
Saml2SigningBehavior,
|
|
||||||
SsoType,
|
|
||||||
} from "../../enums/ssoEnums";
|
|
||||||
import { BaseResponse } from "../response/baseResponse";
|
|
||||||
import { SsoConfigView } from "../view/ssoConfigView";
|
|
||||||
|
|
||||||
export class SsoConfigApi extends BaseResponse {
|
|
||||||
static fromView(view: SsoConfigView, api = new SsoConfigApi()) {
|
|
||||||
api.configType = view.configType;
|
|
||||||
|
|
||||||
api.keyConnectorEnabled = view.keyConnectorEnabled;
|
|
||||||
api.keyConnectorUrl = view.keyConnectorUrl;
|
|
||||||
|
|
||||||
if (api.configType === SsoType.OpenIdConnect) {
|
|
||||||
api.authority = view.openId.authority;
|
|
||||||
api.clientId = view.openId.clientId;
|
|
||||||
api.clientSecret = view.openId.clientSecret;
|
|
||||||
api.metadataAddress = view.openId.metadataAddress;
|
|
||||||
api.redirectBehavior = view.openId.redirectBehavior;
|
|
||||||
api.getClaimsFromUserInfoEndpoint = view.openId.getClaimsFromUserInfoEndpoint;
|
|
||||||
api.additionalScopes = view.openId.additionalScopes;
|
|
||||||
api.additionalUserIdClaimTypes = view.openId.additionalUserIdClaimTypes;
|
|
||||||
api.additionalEmailClaimTypes = view.openId.additionalEmailClaimTypes;
|
|
||||||
api.additionalNameClaimTypes = view.openId.additionalNameClaimTypes;
|
|
||||||
api.acrValues = view.openId.acrValues;
|
|
||||||
api.expectedReturnAcrValue = view.openId.expectedReturnAcrValue;
|
|
||||||
} else if (api.configType === SsoType.Saml2) {
|
|
||||||
api.spNameIdFormat = view.saml.spNameIdFormat;
|
|
||||||
api.spOutboundSigningAlgorithm = view.saml.spOutboundSigningAlgorithm;
|
|
||||||
api.spSigningBehavior = view.saml.spSigningBehavior;
|
|
||||||
api.spMinIncomingSigningAlgorithm = view.saml.spMinIncomingSigningAlgorithm;
|
|
||||||
api.spWantAssertionsSigned = view.saml.spWantAssertionsSigned;
|
|
||||||
api.spValidateCertificates = view.saml.spValidateCertificates;
|
|
||||||
|
|
||||||
api.idpEntityId = view.saml.idpEntityId;
|
|
||||||
api.idpBindingType = view.saml.idpBindingType;
|
|
||||||
api.idpSingleSignOnServiceUrl = view.saml.idpSingleSignOnServiceUrl;
|
|
||||||
api.idpSingleLogoutServiceUrl = view.saml.idpSingleLogoutServiceUrl;
|
|
||||||
api.idpX509PublicCert = view.saml.idpX509PublicCert;
|
|
||||||
api.idpOutboundSigningAlgorithm = view.saml.idpOutboundSigningAlgorithm;
|
|
||||||
api.idpAllowUnsolicitedAuthnResponse = view.saml.idpAllowUnsolicitedAuthnResponse;
|
|
||||||
api.idpWantAuthnRequestsSigned = view.saml.idpWantAuthnRequestsSigned;
|
|
||||||
|
|
||||||
// Value is inverted in the api model (disable instead of allow)
|
|
||||||
api.idpDisableOutboundLogoutRequests = !view.saml.idpAllowOutboundLogoutRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
return api;
|
|
||||||
}
|
|
||||||
configType: SsoType;
|
|
||||||
|
|
||||||
keyConnectorEnabled: boolean;
|
|
||||||
keyConnectorUrl: string;
|
|
||||||
|
|
||||||
// OpenId
|
|
||||||
authority: string;
|
|
||||||
clientId: string;
|
|
||||||
clientSecret: string;
|
|
||||||
metadataAddress: string;
|
|
||||||
redirectBehavior: OpenIdConnectRedirectBehavior;
|
|
||||||
getClaimsFromUserInfoEndpoint: boolean;
|
|
||||||
additionalScopes: string;
|
|
||||||
additionalUserIdClaimTypes: string;
|
|
||||||
additionalEmailClaimTypes: string;
|
|
||||||
additionalNameClaimTypes: string;
|
|
||||||
acrValues: string;
|
|
||||||
expectedReturnAcrValue: string;
|
|
||||||
|
|
||||||
// SAML
|
|
||||||
spNameIdFormat: Saml2NameIdFormat;
|
|
||||||
spOutboundSigningAlgorithm: string;
|
|
||||||
spSigningBehavior: Saml2SigningBehavior;
|
|
||||||
spMinIncomingSigningAlgorithm: boolean;
|
|
||||||
spWantAssertionsSigned: boolean;
|
|
||||||
spValidateCertificates: boolean;
|
|
||||||
|
|
||||||
idpEntityId: string;
|
|
||||||
idpBindingType: Saml2BindingType;
|
|
||||||
idpSingleSignOnServiceUrl: string;
|
|
||||||
idpSingleLogoutServiceUrl: string;
|
|
||||||
idpX509PublicCert: string;
|
|
||||||
idpOutboundSigningAlgorithm: string;
|
|
||||||
idpAllowUnsolicitedAuthnResponse: boolean;
|
|
||||||
idpDisableOutboundLogoutRequests: boolean;
|
|
||||||
idpWantAuthnRequestsSigned: boolean;
|
|
||||||
|
|
||||||
constructor(data: any = null) {
|
|
||||||
super(data);
|
|
||||||
if (data == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.configType = this.getResponseProperty("ConfigType");
|
|
||||||
|
|
||||||
this.keyConnectorEnabled = this.getResponseProperty("KeyConnectorEnabled");
|
|
||||||
this.keyConnectorUrl = this.getResponseProperty("KeyConnectorUrl");
|
|
||||||
|
|
||||||
this.authority = this.getResponseProperty("Authority");
|
|
||||||
this.clientId = this.getResponseProperty("ClientId");
|
|
||||||
this.clientSecret = this.getResponseProperty("ClientSecret");
|
|
||||||
this.metadataAddress = this.getResponseProperty("MetadataAddress");
|
|
||||||
this.redirectBehavior = this.getResponseProperty("RedirectBehavior");
|
|
||||||
this.getClaimsFromUserInfoEndpoint = this.getResponseProperty("GetClaimsFromUserInfoEndpoint");
|
|
||||||
this.additionalScopes = this.getResponseProperty("AdditionalScopes");
|
|
||||||
this.additionalUserIdClaimTypes = this.getResponseProperty("AdditionalUserIdClaimTypes");
|
|
||||||
this.additionalEmailClaimTypes = this.getResponseProperty("AdditionalEmailClaimTypes");
|
|
||||||
this.additionalNameClaimTypes = this.getResponseProperty("AdditionalNameClaimTypes");
|
|
||||||
this.acrValues = this.getResponseProperty("AcrValues");
|
|
||||||
this.expectedReturnAcrValue = this.getResponseProperty("ExpectedReturnAcrValue");
|
|
||||||
|
|
||||||
this.spNameIdFormat = this.getResponseProperty("SpNameIdFormat");
|
|
||||||
this.spOutboundSigningAlgorithm = this.getResponseProperty("SpOutboundSigningAlgorithm");
|
|
||||||
this.spSigningBehavior = this.getResponseProperty("SpSigningBehavior");
|
|
||||||
this.spMinIncomingSigningAlgorithm = this.getResponseProperty("SpMinIncomingSigningAlgorithm");
|
|
||||||
this.spWantAssertionsSigned = this.getResponseProperty("SpWantAssertionsSigned");
|
|
||||||
this.spValidateCertificates = this.getResponseProperty("SpValidateCertificates");
|
|
||||||
|
|
||||||
this.idpEntityId = this.getResponseProperty("IdpEntityId");
|
|
||||||
this.idpBindingType = this.getResponseProperty("IdpBindingType");
|
|
||||||
this.idpSingleSignOnServiceUrl = this.getResponseProperty("IdpSingleSignOnServiceUrl");
|
|
||||||
this.idpSingleLogoutServiceUrl = this.getResponseProperty("IdpSingleLogoutServiceUrl");
|
|
||||||
this.idpX509PublicCert = this.getResponseProperty("IdpX509PublicCert");
|
|
||||||
this.idpOutboundSigningAlgorithm = this.getResponseProperty("IdpOutboundSigningAlgorithm");
|
|
||||||
this.idpAllowUnsolicitedAuthnResponse = this.getResponseProperty(
|
|
||||||
"IdpAllowUnsolicitedAuthnResponse",
|
|
||||||
);
|
|
||||||
this.idpDisableOutboundLogoutRequests = this.getResponseProperty(
|
|
||||||
"IdpDisableOutboundLogoutRequests",
|
|
||||||
);
|
|
||||||
this.idpWantAuthnRequestsSigned = this.getResponseProperty("IdpWantAuthnRequestsSigned");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
import { OrganizationUserStatusType } from "../../enums/organizationUserStatusType";
|
|
||||||
import { OrganizationUserType } from "../../enums/organizationUserType";
|
|
||||||
import { ProductType } from "../../enums/productType";
|
|
||||||
import { PermissionsApi } from "../api/permissionsApi";
|
|
||||||
import { ProfileOrganizationResponse } from "../response/profileOrganizationResponse";
|
|
||||||
|
|
||||||
export class OrganizationData {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
status: OrganizationUserStatusType;
|
|
||||||
type: OrganizationUserType;
|
|
||||||
enabled: boolean;
|
|
||||||
usePolicies: boolean;
|
|
||||||
useGroups: boolean;
|
|
||||||
useDirectory: boolean;
|
|
||||||
useEvents: boolean;
|
|
||||||
useTotp: boolean;
|
|
||||||
use2fa: boolean;
|
|
||||||
useApi: boolean;
|
|
||||||
useSso: boolean;
|
|
||||||
useKeyConnector: boolean;
|
|
||||||
useResetPassword: boolean;
|
|
||||||
selfHost: boolean;
|
|
||||||
usersGetPremium: boolean;
|
|
||||||
seats: number;
|
|
||||||
maxCollections: number;
|
|
||||||
maxStorageGb?: number;
|
|
||||||
ssoBound: boolean;
|
|
||||||
identifier: string;
|
|
||||||
permissions: PermissionsApi;
|
|
||||||
resetPasswordEnrolled: boolean;
|
|
||||||
userId: string;
|
|
||||||
hasPublicAndPrivateKeys: boolean;
|
|
||||||
providerId: string;
|
|
||||||
providerName: string;
|
|
||||||
isProviderUser: boolean;
|
|
||||||
familySponsorshipFriendlyName: string;
|
|
||||||
familySponsorshipAvailable: boolean;
|
|
||||||
planProductType: ProductType;
|
|
||||||
keyConnectorEnabled: boolean;
|
|
||||||
keyConnectorUrl: string;
|
|
||||||
|
|
||||||
constructor(response: ProfileOrganizationResponse) {
|
|
||||||
this.id = response.id;
|
|
||||||
this.name = response.name;
|
|
||||||
this.status = response.status;
|
|
||||||
this.type = response.type;
|
|
||||||
this.enabled = response.enabled;
|
|
||||||
this.usePolicies = response.usePolicies;
|
|
||||||
this.useGroups = response.useGroups;
|
|
||||||
this.useDirectory = response.useDirectory;
|
|
||||||
this.useEvents = response.useEvents;
|
|
||||||
this.useTotp = response.useTotp;
|
|
||||||
this.use2fa = response.use2fa;
|
|
||||||
this.useApi = response.useApi;
|
|
||||||
this.useSso = response.useSso;
|
|
||||||
this.useKeyConnector = response.useKeyConnector;
|
|
||||||
this.useResetPassword = response.useResetPassword;
|
|
||||||
this.selfHost = response.selfHost;
|
|
||||||
this.usersGetPremium = response.usersGetPremium;
|
|
||||||
this.seats = response.seats;
|
|
||||||
this.maxCollections = response.maxCollections;
|
|
||||||
this.maxStorageGb = response.maxStorageGb;
|
|
||||||
this.ssoBound = response.ssoBound;
|
|
||||||
this.identifier = response.identifier;
|
|
||||||
this.permissions = response.permissions;
|
|
||||||
this.resetPasswordEnrolled = response.resetPasswordEnrolled;
|
|
||||||
this.userId = response.userId;
|
|
||||||
this.hasPublicAndPrivateKeys = response.hasPublicAndPrivateKeys;
|
|
||||||
this.providerId = response.providerId;
|
|
||||||
this.providerName = response.providerName;
|
|
||||||
this.familySponsorshipFriendlyName = response.familySponsorshipFriendlyName;
|
|
||||||
this.familySponsorshipAvailable = response.familySponsorshipAvailable;
|
|
||||||
this.planProductType = response.planProductType;
|
|
||||||
this.keyConnectorEnabled = response.keyConnectorEnabled;
|
|
||||||
this.keyConnectorUrl = response.keyConnectorUrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { ProviderUserStatusType } from "../../enums/providerUserStatusType";
|
|
||||||
import { ProviderUserType } from "../../enums/providerUserType";
|
|
||||||
import { ProfileProviderResponse } from "../response/profileProviderResponse";
|
|
||||||
|
|
||||||
export class ProviderData {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
status: ProviderUserStatusType;
|
|
||||||
type: ProviderUserType;
|
|
||||||
enabled: boolean;
|
|
||||||
userId: string;
|
|
||||||
useEvents: boolean;
|
|
||||||
|
|
||||||
constructor(response: ProfileProviderResponse) {
|
|
||||||
this.id = response.id;
|
|
||||||
this.name = response.name;
|
|
||||||
this.status = response.status;
|
|
||||||
this.type = response.type;
|
|
||||||
this.enabled = response.enabled;
|
|
||||||
this.userId = response.userId;
|
|
||||||
this.useEvents = response.useEvents;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
import { AuthenticationStatus } from "../../enums/authenticationStatus";
|
|
||||||
import { KdfType } from "../../enums/kdfType";
|
|
||||||
import { UriMatchType } from "../../enums/uriMatchType";
|
|
||||||
import { OrganizationData } from "../data/organizationData";
|
|
||||||
import { ProviderData } from "../data/providerData";
|
|
||||||
|
|
||||||
import { EncString } from "./encString";
|
|
||||||
import { EnvironmentUrls } from "./environmentUrls";
|
|
||||||
import { SymmetricCryptoKey } from "./symmetricCryptoKey";
|
|
||||||
|
|
||||||
export class EncryptionPair<TEncrypted, TDecrypted> {
|
|
||||||
encrypted?: TEncrypted;
|
|
||||||
decrypted?: TDecrypted;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DataEncryptionPair<TEncrypted, TDecrypted> {
|
|
||||||
encrypted?: { [id: string]: TEncrypted };
|
|
||||||
decrypted?: TDecrypted[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AccountData {
|
|
||||||
ciphers?: any = new DataEncryptionPair<any, any>();
|
|
||||||
folders?: DataEncryptionPair<any, any> = new DataEncryptionPair<any, any>();
|
|
||||||
localData?: any;
|
|
||||||
sends?: any = new DataEncryptionPair<any, any>();
|
|
||||||
collections?: DataEncryptionPair<any, any> = new DataEncryptionPair<any, any>();
|
|
||||||
policies?: DataEncryptionPair<any, any> = new DataEncryptionPair<any, any>();
|
|
||||||
passwordGenerationHistory?: EncryptionPair<any[], any[]> = new EncryptionPair<any[], any[]>();
|
|
||||||
addEditCipherInfo?: any;
|
|
||||||
eventCollection?: any[];
|
|
||||||
organizations?: { [id: string]: OrganizationData };
|
|
||||||
providers?: { [id: string]: ProviderData };
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AccountKeys {
|
|
||||||
cryptoMasterKey?: SymmetricCryptoKey;
|
|
||||||
cryptoMasterKeyAuto?: string;
|
|
||||||
cryptoMasterKeyB64?: string;
|
|
||||||
cryptoMasterKeyBiometric?: string;
|
|
||||||
cryptoSymmetricKey?: EncryptionPair<string, SymmetricCryptoKey> = new EncryptionPair<
|
|
||||||
string,
|
|
||||||
SymmetricCryptoKey
|
|
||||||
>();
|
|
||||||
organizationKeys?: EncryptionPair<any, Map<string, SymmetricCryptoKey>> = new EncryptionPair<
|
|
||||||
any,
|
|
||||||
Map<string, SymmetricCryptoKey>
|
|
||||||
>();
|
|
||||||
providerKeys?: EncryptionPair<any, Map<string, SymmetricCryptoKey>> = new EncryptionPair<
|
|
||||||
any,
|
|
||||||
Map<string, SymmetricCryptoKey>
|
|
||||||
>();
|
|
||||||
privateKey?: EncryptionPair<string, ArrayBuffer> = new EncryptionPair<string, ArrayBuffer>();
|
|
||||||
legacyEtmKey?: SymmetricCryptoKey;
|
|
||||||
publicKey?: ArrayBuffer;
|
|
||||||
apiKeyClientSecret?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AccountProfile {
|
|
||||||
apiKeyClientId?: string;
|
|
||||||
authenticationStatus?: AuthenticationStatus;
|
|
||||||
convertAccountToKeyConnector?: boolean;
|
|
||||||
email?: string;
|
|
||||||
emailVerified?: boolean;
|
|
||||||
entityId?: string;
|
|
||||||
entityType?: string;
|
|
||||||
everBeenUnlocked?: boolean;
|
|
||||||
forcePasswordReset?: boolean;
|
|
||||||
hasPremiumPersonally?: boolean;
|
|
||||||
lastSync?: string;
|
|
||||||
userId?: string;
|
|
||||||
usesKeyConnector?: boolean;
|
|
||||||
keyHash?: string;
|
|
||||||
kdfIterations?: number;
|
|
||||||
kdfType?: KdfType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AccountSettings {
|
|
||||||
autoConfirmFingerPrints?: boolean;
|
|
||||||
autoFillOnPageLoadDefault?: boolean;
|
|
||||||
biometricLocked?: boolean;
|
|
||||||
biometricUnlock?: boolean;
|
|
||||||
clearClipboard?: number;
|
|
||||||
collapsedGroupings?: string[];
|
|
||||||
defaultUriMatch?: UriMatchType;
|
|
||||||
disableAddLoginNotification?: boolean;
|
|
||||||
disableAutoBiometricsPrompt?: boolean;
|
|
||||||
disableAutoTotpCopy?: boolean;
|
|
||||||
disableBadgeCounter?: boolean;
|
|
||||||
disableChangedPasswordNotification?: boolean;
|
|
||||||
disableContextMenuItem?: boolean;
|
|
||||||
disableGa?: boolean;
|
|
||||||
dontShowCardsCurrentTab?: boolean;
|
|
||||||
dontShowIdentitiesCurrentTab?: boolean;
|
|
||||||
enableAlwaysOnTop?: boolean;
|
|
||||||
enableAutoFillOnPageLoad?: boolean;
|
|
||||||
enableBiometric?: boolean;
|
|
||||||
enableFullWidth?: boolean;
|
|
||||||
enableGravitars?: boolean;
|
|
||||||
environmentUrls: EnvironmentUrls = new EnvironmentUrls();
|
|
||||||
equivalentDomains?: any;
|
|
||||||
minimizeOnCopyToClipboard?: boolean;
|
|
||||||
neverDomains?: { [id: string]: any };
|
|
||||||
passwordGenerationOptions?: any;
|
|
||||||
usernameGenerationOptions?: any;
|
|
||||||
generatorOptions?: any;
|
|
||||||
pinProtected?: EncryptionPair<string, EncString> = new EncryptionPair<string, EncString>();
|
|
||||||
protectedPin?: string;
|
|
||||||
settings?: any; // TODO: Merge whatever is going on here into the AccountSettings model properly
|
|
||||||
vaultTimeout?: number;
|
|
||||||
vaultTimeoutAction?: string = "lock";
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AccountTokens {
|
|
||||||
accessToken?: string;
|
|
||||||
decodedToken?: any;
|
|
||||||
refreshToken?: string;
|
|
||||||
securityStamp?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Account {
|
|
||||||
data?: AccountData = new AccountData();
|
|
||||||
keys?: AccountKeys = new AccountKeys();
|
|
||||||
profile?: AccountProfile = new AccountProfile();
|
|
||||||
settings?: AccountSettings = new AccountSettings();
|
|
||||||
tokens?: AccountTokens = new AccountTokens();
|
|
||||||
|
|
||||||
constructor(init: Partial<Account>) {
|
|
||||||
Object.assign(this, {
|
|
||||||
data: {
|
|
||||||
...new AccountData(),
|
|
||||||
...init?.data,
|
|
||||||
},
|
|
||||||
keys: {
|
|
||||||
...new AccountKeys(),
|
|
||||||
...init?.keys,
|
|
||||||
},
|
|
||||||
profile: {
|
|
||||||
...new AccountProfile(),
|
|
||||||
...init?.profile,
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
...new AccountSettings(),
|
|
||||||
...init?.settings,
|
|
||||||
},
|
|
||||||
tokens: {
|
|
||||||
...new AccountTokens(),
|
|
||||||
...init?.tokens,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
import { View } from "../view/view";
|
|
||||||
|
|
||||||
import { EncString } from "./encString";
|
|
||||||
import { SymmetricCryptoKey } from "./symmetricCryptoKey";
|
|
||||||
|
|
||||||
export default class Domain {
|
|
||||||
protected buildDomainModel<D extends Domain>(
|
|
||||||
domain: D,
|
|
||||||
dataObj: any,
|
|
||||||
map: any,
|
|
||||||
notEncList: any[] = [],
|
|
||||||
) {
|
|
||||||
for (const prop in map) {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
if (!map.hasOwnProperty(prop)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const objProp = dataObj[map[prop] || prop];
|
|
||||||
if (notEncList.indexOf(prop) > -1) {
|
|
||||||
(domain as any)[prop] = objProp ? objProp : null;
|
|
||||||
} else {
|
|
||||||
(domain as any)[prop] = objProp ? new EncString(objProp) : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
protected buildDataModel<D extends Domain>(
|
|
||||||
domain: D,
|
|
||||||
dataObj: any,
|
|
||||||
map: any,
|
|
||||||
notEncStringList: any[] = [],
|
|
||||||
) {
|
|
||||||
for (const prop in map) {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
if (!map.hasOwnProperty(prop)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const objProp = (domain as any)[map[prop] || prop];
|
|
||||||
if (notEncStringList.indexOf(prop) > -1) {
|
|
||||||
(dataObj as any)[prop] = objProp != null ? objProp : null;
|
|
||||||
} else {
|
|
||||||
(dataObj as any)[prop] = objProp != null ? (objProp as EncString).encryptedString : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async decryptObj<T extends View>(
|
|
||||||
viewModel: T,
|
|
||||||
map: any,
|
|
||||||
orgId: string,
|
|
||||||
key: SymmetricCryptoKey = null,
|
|
||||||
): Promise<T> {
|
|
||||||
const promises = [];
|
|
||||||
const self: any = this;
|
|
||||||
|
|
||||||
for (const prop in map) {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
if (!map.hasOwnProperty(prop)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
(function (theProp) {
|
|
||||||
const p = Promise.resolve()
|
|
||||||
.then(() => {
|
|
||||||
const mapProp = map[theProp] || theProp;
|
|
||||||
if (self[mapProp]) {
|
|
||||||
return self[mapProp].decrypt(orgId, key);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
.then((val: any) => {
|
|
||||||
(viewModel as any)[theProp] = val;
|
|
||||||
});
|
|
||||||
promises.push(p);
|
|
||||||
})(prop);
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(promises);
|
|
||||||
return viewModel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export class EncArrayBuffer {
|
|
||||||
constructor(public buffer: ArrayBuffer) {}
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
import { CryptoService } from "../../abstractions/crypto.service";
|
|
||||||
import { EncryptionType } from "../../enums/encryptionType";
|
|
||||||
import { Utils } from "../../misc/utils";
|
|
||||||
|
|
||||||
import { SymmetricCryptoKey } from "./symmetricCryptoKey";
|
|
||||||
|
|
||||||
export class EncString {
|
|
||||||
encryptedString?: string;
|
|
||||||
encryptionType?: EncryptionType;
|
|
||||||
decryptedValue?: string;
|
|
||||||
data?: string;
|
|
||||||
iv?: string;
|
|
||||||
mac?: string;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
encryptedStringOrType: string | EncryptionType,
|
|
||||||
data?: string,
|
|
||||||
iv?: string,
|
|
||||||
mac?: string,
|
|
||||||
) {
|
|
||||||
if (data != null) {
|
|
||||||
// data and header
|
|
||||||
const encType = encryptedStringOrType as EncryptionType;
|
|
||||||
|
|
||||||
if (iv != null) {
|
|
||||||
this.encryptedString = encType + "." + iv + "|" + data;
|
|
||||||
} else {
|
|
||||||
this.encryptedString = encType + "." + data;
|
|
||||||
}
|
|
||||||
|
|
||||||
// mac
|
|
||||||
if (mac != null) {
|
|
||||||
this.encryptedString += "|" + mac;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.encryptionType = encType;
|
|
||||||
this.data = data;
|
|
||||||
this.iv = iv;
|
|
||||||
this.mac = mac;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.encryptedString = encryptedStringOrType as string;
|
|
||||||
if (!this.encryptedString) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const headerPieces = this.encryptedString.split(".");
|
|
||||||
let encPieces: string[] = null;
|
|
||||||
|
|
||||||
if (headerPieces.length === 2) {
|
|
||||||
try {
|
|
||||||
this.encryptionType = parseInt(headerPieces[0], null);
|
|
||||||
encPieces = headerPieces[1].split("|");
|
|
||||||
} catch {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
encPieces = this.encryptedString.split("|");
|
|
||||||
this.encryptionType =
|
|
||||||
encPieces.length === 3
|
|
||||||
? EncryptionType.AesCbc128_HmacSha256_B64
|
|
||||||
: EncryptionType.AesCbc256_B64;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (this.encryptionType) {
|
|
||||||
case EncryptionType.AesCbc128_HmacSha256_B64:
|
|
||||||
case EncryptionType.AesCbc256_HmacSha256_B64:
|
|
||||||
if (encPieces.length !== 3) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.iv = encPieces[0];
|
|
||||||
this.data = encPieces[1];
|
|
||||||
this.mac = encPieces[2];
|
|
||||||
break;
|
|
||||||
case EncryptionType.AesCbc256_B64:
|
|
||||||
if (encPieces.length !== 2) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.iv = encPieces[0];
|
|
||||||
this.data = encPieces[1];
|
|
||||||
break;
|
|
||||||
case EncryptionType.Rsa2048_OaepSha256_B64:
|
|
||||||
case EncryptionType.Rsa2048_OaepSha1_B64:
|
|
||||||
if (encPieces.length !== 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.data = encPieces[0];
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async decrypt(orgId: string, key: SymmetricCryptoKey = null): Promise<string> {
|
|
||||||
if (this.decryptedValue != null) {
|
|
||||||
return this.decryptedValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cryptoService: CryptoService;
|
|
||||||
const containerService = (Utils.global as any).bitwardenContainerService;
|
|
||||||
if (containerService) {
|
|
||||||
cryptoService = containerService.getCryptoService();
|
|
||||||
} else {
|
|
||||||
throw new Error("global bitwardenContainerService not initialized.");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (key == null) {
|
|
||||||
key = await cryptoService.getOrgKey(orgId);
|
|
||||||
}
|
|
||||||
this.decryptedValue = await cryptoService.decryptToUtf8(this, key);
|
|
||||||
} catch {
|
|
||||||
this.decryptedValue = "[error: cannot decrypt]";
|
|
||||||
}
|
|
||||||
return this.decryptedValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { SymmetricCryptoKey } from "./symmetricCryptoKey";
|
|
||||||
|
|
||||||
export class EncryptedObject {
|
|
||||||
iv: ArrayBuffer;
|
|
||||||
data: ArrayBuffer;
|
|
||||||
mac: ArrayBuffer;
|
|
||||||
key: SymmetricCryptoKey;
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,5 @@ export class EnvironmentUrls {
|
|||||||
base: string = null;
|
base: string = null;
|
||||||
api: string = null;
|
api: string = null;
|
||||||
identity: string = null;
|
identity: string = null;
|
||||||
icons: string = null;
|
|
||||||
notifications: string = null;
|
|
||||||
events: string = null;
|
|
||||||
webVault: string = null;
|
webVault: string = null;
|
||||||
keyConnector: string = null;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
import { StateVersion } from "../../enums/stateVersion";
|
|
||||||
import { ThemeType } from "../../enums/themeType";
|
|
||||||
|
|
||||||
import { EnvironmentUrls } from "./environmentUrls";
|
|
||||||
import { WindowState } from "./windowState";
|
|
||||||
|
|
||||||
export class GlobalState {
|
|
||||||
enableAlwaysOnTop?: boolean;
|
|
||||||
installedVersion?: string;
|
|
||||||
locale?: string = "en";
|
|
||||||
organizationInvitation?: any;
|
|
||||||
ssoCodeVerifier?: string;
|
|
||||||
ssoOrganizationIdentifier?: string;
|
|
||||||
ssoState?: string;
|
|
||||||
rememberedEmail?: string;
|
|
||||||
theme?: ThemeType = ThemeType.System;
|
|
||||||
window?: WindowState = new WindowState();
|
|
||||||
twoFactorToken?: string;
|
|
||||||
disableFavicon?: boolean;
|
|
||||||
biometricAwaitingAcceptance?: boolean;
|
|
||||||
biometricFingerprintValidated?: boolean;
|
|
||||||
vaultTimeout?: number;
|
|
||||||
vaultTimeoutAction?: string;
|
|
||||||
loginRedirect?: any;
|
|
||||||
mainWindowSize?: number;
|
|
||||||
enableBiometrics?: boolean;
|
|
||||||
biometricText?: string;
|
|
||||||
noAutoPromptBiometrics?: boolean;
|
|
||||||
noAutoPromptBiometricsText?: string;
|
|
||||||
stateVersion: StateVersion = StateVersion.One;
|
|
||||||
environmentUrls: EnvironmentUrls = new EnvironmentUrls();
|
|
||||||
enableTray?: boolean;
|
|
||||||
enableMinimizeToTray?: boolean;
|
|
||||||
enableCloseToTray?: boolean;
|
|
||||||
enableStartToTray?: boolean;
|
|
||||||
openAtLogin?: boolean;
|
|
||||||
alwaysShowDock?: boolean;
|
|
||||||
enableBrowserIntegration?: boolean;
|
|
||||||
enableBrowserIntegrationFingerprint?: boolean;
|
|
||||||
}
|
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
import { OrganizationUserStatusType } from "../../enums/organizationUserStatusType";
|
|
||||||
import { OrganizationUserType } from "../../enums/organizationUserType";
|
|
||||||
import { ProductType } from "../../enums/productType";
|
|
||||||
import { PermissionsApi } from "../api/permissionsApi";
|
|
||||||
import { OrganizationData } from "../data/organizationData";
|
|
||||||
|
|
||||||
export class Organization {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
status: OrganizationUserStatusType;
|
|
||||||
type: OrganizationUserType;
|
|
||||||
enabled: boolean;
|
|
||||||
usePolicies: boolean;
|
|
||||||
useGroups: boolean;
|
|
||||||
useDirectory: boolean;
|
|
||||||
useEvents: boolean;
|
|
||||||
useTotp: boolean;
|
|
||||||
use2fa: boolean;
|
|
||||||
useApi: boolean;
|
|
||||||
useSso: boolean;
|
|
||||||
useKeyConnector: boolean;
|
|
||||||
useResetPassword: boolean;
|
|
||||||
selfHost: boolean;
|
|
||||||
usersGetPremium: boolean;
|
|
||||||
seats: number;
|
|
||||||
maxCollections: number;
|
|
||||||
maxStorageGb?: number;
|
|
||||||
ssoBound: boolean;
|
|
||||||
identifier: string;
|
|
||||||
permissions: PermissionsApi;
|
|
||||||
resetPasswordEnrolled: boolean;
|
|
||||||
userId: string;
|
|
||||||
hasPublicAndPrivateKeys: boolean;
|
|
||||||
providerId: string;
|
|
||||||
providerName: string;
|
|
||||||
isProviderUser: boolean;
|
|
||||||
familySponsorshipFriendlyName: string;
|
|
||||||
familySponsorshipAvailable: boolean;
|
|
||||||
planProductType: ProductType;
|
|
||||||
keyConnectorEnabled: boolean;
|
|
||||||
keyConnectorUrl: string;
|
|
||||||
|
|
||||||
constructor(obj?: OrganizationData) {
|
|
||||||
if (obj == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.id = obj.id;
|
|
||||||
this.name = obj.name;
|
|
||||||
this.status = obj.status;
|
|
||||||
this.type = obj.type;
|
|
||||||
this.enabled = obj.enabled;
|
|
||||||
this.usePolicies = obj.usePolicies;
|
|
||||||
this.useGroups = obj.useGroups;
|
|
||||||
this.useDirectory = obj.useDirectory;
|
|
||||||
this.useEvents = obj.useEvents;
|
|
||||||
this.useTotp = obj.useTotp;
|
|
||||||
this.use2fa = obj.use2fa;
|
|
||||||
this.useApi = obj.useApi;
|
|
||||||
this.useSso = obj.useSso;
|
|
||||||
this.useKeyConnector = obj.useKeyConnector;
|
|
||||||
this.useResetPassword = obj.useResetPassword;
|
|
||||||
this.selfHost = obj.selfHost;
|
|
||||||
this.usersGetPremium = obj.usersGetPremium;
|
|
||||||
this.seats = obj.seats;
|
|
||||||
this.maxCollections = obj.maxCollections;
|
|
||||||
this.maxStorageGb = obj.maxStorageGb;
|
|
||||||
this.ssoBound = obj.ssoBound;
|
|
||||||
this.identifier = obj.identifier;
|
|
||||||
this.permissions = obj.permissions;
|
|
||||||
this.resetPasswordEnrolled = obj.resetPasswordEnrolled;
|
|
||||||
this.userId = obj.userId;
|
|
||||||
this.hasPublicAndPrivateKeys = obj.hasPublicAndPrivateKeys;
|
|
||||||
this.providerId = obj.providerId;
|
|
||||||
this.providerName = obj.providerName;
|
|
||||||
this.isProviderUser = obj.isProviderUser;
|
|
||||||
this.familySponsorshipFriendlyName = obj.familySponsorshipFriendlyName;
|
|
||||||
this.familySponsorshipAvailable = obj.familySponsorshipAvailable;
|
|
||||||
this.planProductType = obj.planProductType;
|
|
||||||
this.keyConnectorEnabled = obj.keyConnectorEnabled;
|
|
||||||
this.keyConnectorUrl = obj.keyConnectorUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
get canAccess() {
|
|
||||||
if (this.type === OrganizationUserType.Owner) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return this.enabled && this.status === OrganizationUserStatusType.Confirmed;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isManager() {
|
|
||||||
return (
|
|
||||||
this.type === OrganizationUserType.Manager ||
|
|
||||||
this.type === OrganizationUserType.Owner ||
|
|
||||||
this.type === OrganizationUserType.Admin
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get isAdmin() {
|
|
||||||
return this.type === OrganizationUserType.Owner || this.type === OrganizationUserType.Admin;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isOwner() {
|
|
||||||
return this.type === OrganizationUserType.Owner || this.isProviderUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
get canAccessEventLogs() {
|
|
||||||
return this.isAdmin || this.permissions.accessEventLogs;
|
|
||||||
}
|
|
||||||
|
|
||||||
get canAccessImportExport() {
|
|
||||||
return this.isAdmin || this.permissions.accessImportExport;
|
|
||||||
}
|
|
||||||
|
|
||||||
get canAccessReports() {
|
|
||||||
return this.isAdmin || this.permissions.accessReports;
|
|
||||||
}
|
|
||||||
|
|
||||||
get canCreateNewCollections() {
|
|
||||||
return (
|
|
||||||
this.isManager ||
|
|
||||||
(this.permissions.createNewCollections ?? this.permissions.manageAllCollections)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get canEditAnyCollection() {
|
|
||||||
return (
|
|
||||||
this.isAdmin || (this.permissions.editAnyCollection ?? this.permissions.manageAllCollections)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get canDeleteAnyCollection() {
|
|
||||||
return (
|
|
||||||
this.isAdmin ||
|
|
||||||
(this.permissions.deleteAnyCollection ?? this.permissions.manageAllCollections)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get canViewAllCollections() {
|
|
||||||
return this.canCreateNewCollections || this.canEditAnyCollection || this.canDeleteAnyCollection;
|
|
||||||
}
|
|
||||||
|
|
||||||
get canEditAssignedCollections() {
|
|
||||||
return (
|
|
||||||
this.isManager ||
|
|
||||||
(this.permissions.editAssignedCollections ?? this.permissions.manageAssignedCollections)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get canDeleteAssignedCollections() {
|
|
||||||
return (
|
|
||||||
this.isManager ||
|
|
||||||
(this.permissions.deleteAssignedCollections ?? this.permissions.manageAssignedCollections)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get canViewAssignedCollections() {
|
|
||||||
return this.canDeleteAssignedCollections || this.canEditAssignedCollections;
|
|
||||||
}
|
|
||||||
|
|
||||||
get canManageGroups() {
|
|
||||||
return this.isAdmin || this.permissions.manageGroups;
|
|
||||||
}
|
|
||||||
|
|
||||||
get canManageSso() {
|
|
||||||
return this.isAdmin || this.permissions.manageSso;
|
|
||||||
}
|
|
||||||
|
|
||||||
get canManagePolicies() {
|
|
||||||
return this.isAdmin || this.permissions.managePolicies;
|
|
||||||
}
|
|
||||||
|
|
||||||
get canManageUsers() {
|
|
||||||
return this.isAdmin || this.permissions.manageUsers;
|
|
||||||
}
|
|
||||||
|
|
||||||
get canManageUsersPassword() {
|
|
||||||
return this.isAdmin || this.permissions.manageResetPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isExemptFromPolicies() {
|
|
||||||
return this.canManagePolicies;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import { ProviderUserStatusType } from "../../enums/providerUserStatusType";
|
|
||||||
import { ProviderUserType } from "../../enums/providerUserType";
|
|
||||||
import { ProviderData } from "../data/providerData";
|
|
||||||
|
|
||||||
export class Provider {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
status: ProviderUserStatusType;
|
|
||||||
type: ProviderUserType;
|
|
||||||
enabled: boolean;
|
|
||||||
userId: string;
|
|
||||||
useEvents: boolean;
|
|
||||||
|
|
||||||
constructor(obj?: ProviderData) {
|
|
||||||
if (obj == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.id = obj.id;
|
|
||||||
this.name = obj.name;
|
|
||||||
this.status = obj.status;
|
|
||||||
this.type = obj.type;
|
|
||||||
this.enabled = obj.enabled;
|
|
||||||
this.userId = obj.userId;
|
|
||||||
this.useEvents = obj.useEvents;
|
|
||||||
}
|
|
||||||
|
|
||||||
get canAccess() {
|
|
||||||
if (this.isProviderAdmin) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return this.enabled && this.status === ProviderUserStatusType.Confirmed;
|
|
||||||
}
|
|
||||||
|
|
||||||
get canCreateOrganizations() {
|
|
||||||
return this.enabled && this.isProviderAdmin;
|
|
||||||
}
|
|
||||||
|
|
||||||
get canManageUsers() {
|
|
||||||
return this.isProviderAdmin;
|
|
||||||
}
|
|
||||||
|
|
||||||
get canAccessEventLogs() {
|
|
||||||
return this.isProviderAdmin;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isProviderAdmin() {
|
|
||||||
return this.type === ProviderUserType.ProviderAdmin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { Account } from "./account";
|
|
||||||
import { GlobalState } from "./globalState";
|
|
||||||
|
|
||||||
export class State<
|
|
||||||
TGlobalState extends GlobalState = GlobalState,
|
|
||||||
TAccount extends Account = Account,
|
|
||||||
> {
|
|
||||||
accounts: { [userId: string]: TAccount } = {};
|
|
||||||
globals: TGlobalState;
|
|
||||||
activeUserId: string;
|
|
||||||
authenticatedAccounts: string[] = [];
|
|
||||||
accountActivity: { [userId: string]: number } = {};
|
|
||||||
|
|
||||||
constructor(globals: TGlobalState) {
|
|
||||||
this.globals = globals;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
export class TreeNode<T extends ITreeNodeObject> {
|
|
||||||
parent: T;
|
|
||||||
node: T;
|
|
||||||
children: TreeNode<T>[] = [];
|
|
||||||
|
|
||||||
constructor(node: T, name: string, parent: T) {
|
|
||||||
this.parent = parent;
|
|
||||||
this.node = node;
|
|
||||||
this.node.name = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ITreeNodeObject {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { BaseResponse } from "./baseResponse";
|
|
||||||
|
|
||||||
export class ApiKeyResponse extends BaseResponse {
|
|
||||||
apiKey: string;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.apiKey = this.getResponseProperty("ApiKey");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
import { PaymentMethodType } from "../../enums/paymentMethodType";
|
|
||||||
import { TransactionType } from "../../enums/transactionType";
|
|
||||||
|
|
||||||
import { BaseResponse } from "./baseResponse";
|
|
||||||
|
|
||||||
export class BillingResponse extends BaseResponse {
|
|
||||||
balance: number;
|
|
||||||
paymentSource: BillingSourceResponse;
|
|
||||||
invoices: BillingInvoiceResponse[] = [];
|
|
||||||
transactions: BillingTransactionResponse[] = [];
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.balance = this.getResponseProperty("Balance");
|
|
||||||
const paymentSource = this.getResponseProperty("PaymentSource");
|
|
||||||
const transactions = this.getResponseProperty("Transactions");
|
|
||||||
const invoices = this.getResponseProperty("Invoices");
|
|
||||||
this.paymentSource = paymentSource == null ? null : new BillingSourceResponse(paymentSource);
|
|
||||||
if (transactions != null) {
|
|
||||||
this.transactions = transactions.map((t: any) => new BillingTransactionResponse(t));
|
|
||||||
}
|
|
||||||
if (invoices != null) {
|
|
||||||
this.invoices = invoices.map((i: any) => new BillingInvoiceResponse(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BillingSourceResponse extends BaseResponse {
|
|
||||||
type: PaymentMethodType;
|
|
||||||
cardBrand: string;
|
|
||||||
description: string;
|
|
||||||
needsVerification: boolean;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.type = this.getResponseProperty("Type");
|
|
||||||
this.cardBrand = this.getResponseProperty("CardBrand");
|
|
||||||
this.description = this.getResponseProperty("Description");
|
|
||||||
this.needsVerification = this.getResponseProperty("NeedsVerification");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BillingInvoiceResponse extends BaseResponse {
|
|
||||||
url: string;
|
|
||||||
pdfUrl: string;
|
|
||||||
number: string;
|
|
||||||
paid: boolean;
|
|
||||||
date: string;
|
|
||||||
amount: number;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.url = this.getResponseProperty("Url");
|
|
||||||
this.pdfUrl = this.getResponseProperty("PdfUrl");
|
|
||||||
this.number = this.getResponseProperty("Number");
|
|
||||||
this.paid = this.getResponseProperty("Paid");
|
|
||||||
this.date = this.getResponseProperty("Date");
|
|
||||||
this.amount = this.getResponseProperty("Amount");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BillingTransactionResponse extends BaseResponse {
|
|
||||||
createdDate: string;
|
|
||||||
amount: number;
|
|
||||||
refunded: boolean;
|
|
||||||
partiallyRefunded: boolean;
|
|
||||||
refundedAmount: number;
|
|
||||||
type: TransactionType;
|
|
||||||
paymentMethodType: PaymentMethodType;
|
|
||||||
details: string;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.createdDate = this.getResponseProperty("CreatedDate");
|
|
||||||
this.amount = this.getResponseProperty("Amount");
|
|
||||||
this.refunded = this.getResponseProperty("Refunded");
|
|
||||||
this.partiallyRefunded = this.getResponseProperty("PartiallyRefunded");
|
|
||||||
this.refundedAmount = this.getResponseProperty("RefundedAmount");
|
|
||||||
this.type = this.getResponseProperty("Type");
|
|
||||||
this.paymentMethodType = this.getResponseProperty("PaymentMethodType");
|
|
||||||
this.details = this.getResponseProperty("Details");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { BaseResponse } from "./baseResponse";
|
|
||||||
|
|
||||||
export class BreachAccountResponse extends BaseResponse {
|
|
||||||
addedDate: string;
|
|
||||||
breachDate: string;
|
|
||||||
dataClasses: string[];
|
|
||||||
description: string;
|
|
||||||
domain: string;
|
|
||||||
isActive: boolean;
|
|
||||||
isVerified: boolean;
|
|
||||||
logoPath: string;
|
|
||||||
modifiedDate: string;
|
|
||||||
name: string;
|
|
||||||
pwnCount: number;
|
|
||||||
title: string;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.addedDate = this.getResponseProperty("AddedDate");
|
|
||||||
this.breachDate = this.getResponseProperty("BreachDate");
|
|
||||||
this.dataClasses = this.getResponseProperty("DataClasses");
|
|
||||||
this.description = this.getResponseProperty("Description");
|
|
||||||
this.domain = this.getResponseProperty("Domain");
|
|
||||||
this.isActive = this.getResponseProperty("IsActive");
|
|
||||||
this.isVerified = this.getResponseProperty("IsVerified");
|
|
||||||
this.logoPath = this.getResponseProperty("LogoPath");
|
|
||||||
this.modifiedDate = this.getResponseProperty("ModifiedDate");
|
|
||||||
this.name = this.getResponseProperty("Name");
|
|
||||||
this.pwnCount = this.getResponseProperty("PwnCount");
|
|
||||||
this.title = this.getResponseProperty("Title");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { DeviceType } from "../../enums/deviceType";
|
|
||||||
|
|
||||||
import { BaseResponse } from "./baseResponse";
|
|
||||||
|
|
||||||
export class DeviceResponse extends BaseResponse {
|
|
||||||
id: string;
|
|
||||||
name: number;
|
|
||||||
identifier: string;
|
|
||||||
type: DeviceType;
|
|
||||||
creationDate: string;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.id = this.getResponseProperty("Id");
|
|
||||||
this.name = this.getResponseProperty("Name");
|
|
||||||
this.identifier = this.getResponseProperty("Identifier");
|
|
||||||
this.type = this.getResponseProperty("Type");
|
|
||||||
this.creationDate = this.getResponseProperty("CreationDate");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { BaseResponse } from "./baseResponse";
|
|
||||||
import { GlobalDomainResponse } from "./globalDomainResponse";
|
|
||||||
|
|
||||||
export class DomainsResponse extends BaseResponse {
|
|
||||||
equivalentDomains: string[][];
|
|
||||||
globalEquivalentDomains: GlobalDomainResponse[] = [];
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.equivalentDomains = this.getResponseProperty("EquivalentDomains");
|
|
||||||
const globalEquivalentDomains = this.getResponseProperty("GlobalEquivalentDomains");
|
|
||||||
if (globalEquivalentDomains != null) {
|
|
||||||
this.globalEquivalentDomains = globalEquivalentDomains.map(
|
|
||||||
(d: any) => new GlobalDomainResponse(d),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.globalEquivalentDomains = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { BaseResponse } from "./baseResponse";
|
|
||||||
|
|
||||||
export class GlobalDomainResponse extends BaseResponse {
|
|
||||||
type: number;
|
|
||||||
domains: string[];
|
|
||||||
excluded: boolean;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.type = this.getResponseProperty("Type");
|
|
||||||
this.domains = this.getResponseProperty("Domains");
|
|
||||||
this.excluded = this.getResponseProperty("Excluded");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { BaseResponse } from "./baseResponse";
|
|
||||||
import { SelectionReadOnlyResponse } from "./selectionReadOnlyResponse";
|
|
||||||
|
|
||||||
export class GroupResponse extends BaseResponse {
|
|
||||||
id: string;
|
|
||||||
organizationId: string;
|
|
||||||
name: string;
|
|
||||||
accessAll: boolean;
|
|
||||||
externalId: string;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.id = this.getResponseProperty("Id");
|
|
||||||
this.organizationId = this.getResponseProperty("OrganizationId");
|
|
||||||
this.name = this.getResponseProperty("Name");
|
|
||||||
this.accessAll = this.getResponseProperty("AccessAll");
|
|
||||||
this.externalId = this.getResponseProperty("ExternalId");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class GroupDetailsResponse extends GroupResponse {
|
|
||||||
collections: SelectionReadOnlyResponse[] = [];
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
const collections = this.getResponseProperty("Collections");
|
|
||||||
if (collections != null) {
|
|
||||||
this.collections = collections.map((c: any) => new SelectionReadOnlyResponse(c));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { BaseResponse } from "./baseResponse";
|
|
||||||
|
|
||||||
export class KeyConnectorUserKeyResponse extends BaseResponse {
|
|
||||||
key: string;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.key = this.getResponseProperty("Key");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { BaseResponse } from "./baseResponse";
|
|
||||||
|
|
||||||
export class KeysResponse extends BaseResponse {
|
|
||||||
privateKey: string;
|
|
||||||
publicKey: string;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.privateKey = this.getResponseProperty("PrivateKey");
|
|
||||||
this.publicKey = this.getResponseProperty("PublicKey");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { BaseResponse } from "./baseResponse";
|
|
||||||
|
|
||||||
export class ListResponse<T> extends BaseResponse {
|
|
||||||
data: T[];
|
|
||||||
continuationToken: string;
|
|
||||||
|
|
||||||
constructor(response: any, t: new (dataResponse: any) => T) {
|
|
||||||
super(response);
|
|
||||||
const data = this.getResponseProperty("Data");
|
|
||||||
this.data = data == null ? [] : data.map((dr: any) => new t(dr));
|
|
||||||
this.continuationToken = this.getResponseProperty("ContinuationToken");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
import { NotificationType } from "../../enums/notificationType";
|
|
||||||
|
|
||||||
import { BaseResponse } from "./baseResponse";
|
|
||||||
|
|
||||||
export class NotificationResponse extends BaseResponse {
|
|
||||||
contextId: string;
|
|
||||||
type: NotificationType;
|
|
||||||
payload: any;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.contextId = this.getResponseProperty("ContextId");
|
|
||||||
this.type = this.getResponseProperty("Type");
|
|
||||||
|
|
||||||
const payload = this.getResponseProperty("Payload");
|
|
||||||
switch (this.type) {
|
|
||||||
case NotificationType.SyncCipherCreate:
|
|
||||||
case NotificationType.SyncCipherDelete:
|
|
||||||
case NotificationType.SyncCipherUpdate:
|
|
||||||
case NotificationType.SyncLoginDelete:
|
|
||||||
this.payload = new SyncCipherNotification(payload);
|
|
||||||
break;
|
|
||||||
case NotificationType.SyncFolderCreate:
|
|
||||||
case NotificationType.SyncFolderDelete:
|
|
||||||
case NotificationType.SyncFolderUpdate:
|
|
||||||
this.payload = new SyncFolderNotification(payload);
|
|
||||||
break;
|
|
||||||
case NotificationType.SyncVault:
|
|
||||||
case NotificationType.SyncCiphers:
|
|
||||||
case NotificationType.SyncOrgKeys:
|
|
||||||
case NotificationType.SyncSettings:
|
|
||||||
case NotificationType.LogOut:
|
|
||||||
this.payload = new UserNotification(payload);
|
|
||||||
break;
|
|
||||||
case NotificationType.SyncSendCreate:
|
|
||||||
case NotificationType.SyncSendUpdate:
|
|
||||||
case NotificationType.SyncSendDelete:
|
|
||||||
this.payload = new SyncSendNotification(payload);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SyncCipherNotification extends BaseResponse {
|
|
||||||
id: string;
|
|
||||||
userId: string;
|
|
||||||
organizationId: string;
|
|
||||||
collectionIds: string[];
|
|
||||||
revisionDate: Date;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.id = this.getResponseProperty("Id");
|
|
||||||
this.userId = this.getResponseProperty("UserId");
|
|
||||||
this.organizationId = this.getResponseProperty("OrganizationId");
|
|
||||||
this.collectionIds = this.getResponseProperty("CollectionIds");
|
|
||||||
this.revisionDate = new Date(this.getResponseProperty("RevisionDate"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SyncFolderNotification extends BaseResponse {
|
|
||||||
id: string;
|
|
||||||
userId: string;
|
|
||||||
revisionDate: Date;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.id = this.getResponseProperty("Id");
|
|
||||||
this.userId = this.getResponseProperty("UserId");
|
|
||||||
this.revisionDate = new Date(this.getResponseProperty("RevisionDate"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UserNotification extends BaseResponse {
|
|
||||||
userId: string;
|
|
||||||
date: Date;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.userId = this.getResponseProperty("UserId");
|
|
||||||
this.date = new Date(this.getResponseProperty("Date"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SyncSendNotification extends BaseResponse {
|
|
||||||
id: string;
|
|
||||||
userId: string;
|
|
||||||
revisionDate: Date;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.id = this.getResponseProperty("Id");
|
|
||||||
this.userId = this.getResponseProperty("UserId");
|
|
||||||
this.revisionDate = new Date(this.getResponseProperty("RevisionDate"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import { SsoConfigApi } from "../../api/ssoConfigApi";
|
|
||||||
import { BaseResponse } from "../baseResponse";
|
|
||||||
|
|
||||||
export class OrganizationSsoResponse extends BaseResponse {
|
|
||||||
enabled: boolean;
|
|
||||||
data: SsoConfigApi;
|
|
||||||
urls: SsoUrls;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.enabled = this.getResponseProperty("Enabled");
|
|
||||||
this.data =
|
|
||||||
this.getResponseProperty("Data") != null
|
|
||||||
? new SsoConfigApi(this.getResponseProperty("Data"))
|
|
||||||
: null;
|
|
||||||
this.urls = new SsoUrls(this.getResponseProperty("Urls"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SsoUrls extends BaseResponse {
|
|
||||||
callbackPath: string;
|
|
||||||
signedOutCallbackPath: string;
|
|
||||||
spEntityId: string;
|
|
||||||
spMetadataUrl: string;
|
|
||||||
spAcsUrl: string;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.callbackPath = this.getResponseProperty("CallbackPath");
|
|
||||||
this.signedOutCallbackPath = this.getResponseProperty("SignedOutCallbackPath");
|
|
||||||
this.spEntityId = this.getResponseProperty("SpEntityId");
|
|
||||||
this.spMetadataUrl = this.getResponseProperty("SpMetadataUrl");
|
|
||||||
this.spAcsUrl = this.getResponseProperty("SpAcsUrl");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { BaseResponse } from "./baseResponse";
|
|
||||||
|
|
||||||
export class OrganizationAutoEnrollStatusResponse extends BaseResponse {
|
|
||||||
id: string;
|
|
||||||
resetPasswordEnabled: boolean;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.id = this.getResponseProperty("Id");
|
|
||||||
this.resetPasswordEnabled = this.getResponseProperty("ResetPasswordEnabled");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { KeysResponse } from "./keysResponse";
|
|
||||||
|
|
||||||
export class OrganizationKeysResponse extends KeysResponse {
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import { PlanType } from "../../enums/planType";
|
|
||||||
|
|
||||||
import { BaseResponse } from "./baseResponse";
|
|
||||||
import { PlanResponse } from "./planResponse";
|
|
||||||
|
|
||||||
export class OrganizationResponse extends BaseResponse {
|
|
||||||
id: string;
|
|
||||||
identifier: string;
|
|
||||||
name: string;
|
|
||||||
businessName: string;
|
|
||||||
businessAddress1: string;
|
|
||||||
businessAddress2: string;
|
|
||||||
businessAddress3: string;
|
|
||||||
businessCountry: string;
|
|
||||||
businessTaxNumber: string;
|
|
||||||
billingEmail: string;
|
|
||||||
plan: PlanResponse;
|
|
||||||
planType: PlanType;
|
|
||||||
seats: number;
|
|
||||||
maxAutoscaleSeats: number;
|
|
||||||
maxCollections: number;
|
|
||||||
maxStorageGb: number;
|
|
||||||
useGroups: boolean;
|
|
||||||
useDirectory: boolean;
|
|
||||||
useEvents: boolean;
|
|
||||||
useTotp: boolean;
|
|
||||||
use2fa: boolean;
|
|
||||||
useApi: boolean;
|
|
||||||
useResetPassword: boolean;
|
|
||||||
hasPublicAndPrivateKeys: boolean;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.id = this.getResponseProperty("Id");
|
|
||||||
this.identifier = this.getResponseProperty("Identifier");
|
|
||||||
this.name = this.getResponseProperty("Name");
|
|
||||||
this.businessName = this.getResponseProperty("BusinessName");
|
|
||||||
this.businessAddress1 = this.getResponseProperty("BusinessAddress1");
|
|
||||||
this.businessAddress2 = this.getResponseProperty("BusinessAddress2");
|
|
||||||
this.businessAddress3 = this.getResponseProperty("BusinessAddress3");
|
|
||||||
this.businessCountry = this.getResponseProperty("BusinessCountry");
|
|
||||||
this.businessTaxNumber = this.getResponseProperty("BusinessTaxNumber");
|
|
||||||
this.billingEmail = this.getResponseProperty("BillingEmail");
|
|
||||||
const plan = this.getResponseProperty("Plan");
|
|
||||||
this.plan = plan == null ? null : new PlanResponse(plan);
|
|
||||||
this.planType = this.getResponseProperty("PlanType");
|
|
||||||
this.seats = this.getResponseProperty("Seats");
|
|
||||||
this.maxAutoscaleSeats = this.getResponseProperty("MaxAutoscaleSeats");
|
|
||||||
this.maxCollections = this.getResponseProperty("MaxCollections");
|
|
||||||
this.maxStorageGb = this.getResponseProperty("MaxStorageGb");
|
|
||||||
this.useGroups = this.getResponseProperty("UseGroups");
|
|
||||||
this.useDirectory = this.getResponseProperty("UseDirectory");
|
|
||||||
this.useEvents = this.getResponseProperty("UseEvents");
|
|
||||||
this.useTotp = this.getResponseProperty("UseTotp");
|
|
||||||
this.use2fa = this.getResponseProperty("Use2fa");
|
|
||||||
this.useApi = this.getResponseProperty("UseApi");
|
|
||||||
this.useResetPassword = this.getResponseProperty("UseResetPassword");
|
|
||||||
this.hasPublicAndPrivateKeys = this.getResponseProperty("HasPublicAndPrivateKeys");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { OrganizationResponse } from "./organizationResponse";
|
|
||||||
import {
|
|
||||||
BillingSubscriptionResponse,
|
|
||||||
BillingSubscriptionUpcomingInvoiceResponse,
|
|
||||||
} from "./subscriptionResponse";
|
|
||||||
|
|
||||||
export class OrganizationSubscriptionResponse extends OrganizationResponse {
|
|
||||||
storageName: string;
|
|
||||||
storageGb: number;
|
|
||||||
subscription: BillingSubscriptionResponse;
|
|
||||||
upcomingInvoice: BillingSubscriptionUpcomingInvoiceResponse;
|
|
||||||
expiration: string;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.storageName = this.getResponseProperty("StorageName");
|
|
||||||
this.storageGb = this.getResponseProperty("StorageGb");
|
|
||||||
const subscription = this.getResponseProperty("Subscription");
|
|
||||||
this.subscription = subscription == null ? null : new BillingSubscriptionResponse(subscription);
|
|
||||||
const upcomingInvoice = this.getResponseProperty("UpcomingInvoice");
|
|
||||||
this.upcomingInvoice =
|
|
||||||
upcomingInvoice == null
|
|
||||||
? null
|
|
||||||
: new BillingSubscriptionUpcomingInvoiceResponse(upcomingInvoice);
|
|
||||||
this.expiration = this.getResponseProperty("Expiration");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { BaseResponse } from "./baseResponse";
|
|
||||||
|
|
||||||
export class OrganizationUserBulkPublicKeyResponse extends BaseResponse {
|
|
||||||
id: string;
|
|
||||||
userId: string;
|
|
||||||
key: string;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.id = this.getResponseProperty("Id");
|
|
||||||
this.userId = this.getResponseProperty("UserId");
|
|
||||||
this.key = this.getResponseProperty("Key");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { BaseResponse } from "./baseResponse";
|
|
||||||
|
|
||||||
export class OrganizationUserBulkResponse extends BaseResponse {
|
|
||||||
id: string;
|
|
||||||
error: string;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.id = this.getResponseProperty("Id");
|
|
||||||
this.error = this.getResponseProperty("Error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
import { KdfType } from "../../enums/kdfType";
|
|
||||||
import { OrganizationUserStatusType } from "../../enums/organizationUserStatusType";
|
|
||||||
import { OrganizationUserType } from "../../enums/organizationUserType";
|
|
||||||
import { PermissionsApi } from "../api/permissionsApi";
|
|
||||||
|
|
||||||
import { BaseResponse } from "./baseResponse";
|
|
||||||
import { SelectionReadOnlyResponse } from "./selectionReadOnlyResponse";
|
|
||||||
|
|
||||||
export class OrganizationUserResponse extends BaseResponse {
|
|
||||||
id: string;
|
|
||||||
userId: string;
|
|
||||||
type: OrganizationUserType;
|
|
||||||
status: OrganizationUserStatusType;
|
|
||||||
accessAll: boolean;
|
|
||||||
permissions: PermissionsApi;
|
|
||||||
resetPasswordEnrolled: boolean;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.id = this.getResponseProperty("Id");
|
|
||||||
this.userId = this.getResponseProperty("UserId");
|
|
||||||
this.type = this.getResponseProperty("Type");
|
|
||||||
this.status = this.getResponseProperty("Status");
|
|
||||||
this.permissions = new PermissionsApi(this.getResponseProperty("Permissions"));
|
|
||||||
this.accessAll = this.getResponseProperty("AccessAll");
|
|
||||||
this.resetPasswordEnrolled = this.getResponseProperty("ResetPasswordEnrolled");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class OrganizationUserUserDetailsResponse extends OrganizationUserResponse {
|
|
||||||
name: string;
|
|
||||||
email: string;
|
|
||||||
twoFactorEnabled: boolean;
|
|
||||||
usesKeyConnector: boolean;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.name = this.getResponseProperty("Name");
|
|
||||||
this.email = this.getResponseProperty("Email");
|
|
||||||
this.twoFactorEnabled = this.getResponseProperty("TwoFactorEnabled");
|
|
||||||
this.usesKeyConnector = this.getResponseProperty("UsesKeyConnector") ?? false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class OrganizationUserDetailsResponse extends OrganizationUserResponse {
|
|
||||||
collections: SelectionReadOnlyResponse[] = [];
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
const collections = this.getResponseProperty("Collections");
|
|
||||||
if (collections != null) {
|
|
||||||
this.collections = collections.map((c: any) => new SelectionReadOnlyResponse(c));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class OrganizationUserResetPasswordDetailsReponse extends BaseResponse {
|
|
||||||
kdf: KdfType;
|
|
||||||
kdfIterations: number;
|
|
||||||
resetPasswordKey: string;
|
|
||||||
encryptedPrivateKey: string;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.kdf = this.getResponseProperty("Kdf");
|
|
||||||
this.kdfIterations = this.getResponseProperty("KdfIterations");
|
|
||||||
this.resetPasswordKey = this.getResponseProperty("ResetPasswordKey");
|
|
||||||
this.encryptedPrivateKey = this.getResponseProperty("EncryptedPrivateKey");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user