mirror of
https://github.com/bitwarden/directory-connector
synced 2026-02-26 01:13:14 +00:00
Compare commits
2 Commits
jslib-remo
...
context-ru
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22fe0bc482 | ||
|
|
5b1dd63c49 |
1187
.claude/CLAUDE.md
1187
.claude/CLAUDE.md
File diff suppressed because it is too large
Load Diff
30
.claude/commands/code-explainer.md
Normal file
30
.claude/commands/code-explainer.md
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
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
239
.claude/plan.md
@@ -1,239 +0,0 @@
|
||||
# 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]
|
||||
@@ -1,130 +0,0 @@
|
||||
---
|
||||
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,3 +9,26 @@
|
||||
## 📸 Screenshots
|
||||
|
||||
<!-- 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 }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -51,12 +51,12 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
@@ -129,12 +129,12 @@ jobs:
|
||||
_NODE_VERSION: ${{ needs.setup.outputs.node_version }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
@@ -200,7 +200,7 @@ jobs:
|
||||
_NODE_VERSION: ${{ needs.setup.outputs.node_version }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -209,7 +209,7 @@ jobs:
|
||||
choco install checksum --no-progress
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
@@ -279,12 +279,12 @@ jobs:
|
||||
HUSKY: 0
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
@@ -379,12 +379,12 @@ jobs:
|
||||
HUSKY: 0
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
@@ -439,12 +439,12 @@ jobs:
|
||||
HUSKY: 0
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
|
||||
10
.github/workflows/integration-test.yml
vendored
10
.github/workflows/integration-test.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
@@ -129,7 +129,7 @@ jobs:
|
||||
|
||||
- name: Report test results
|
||||
id: report
|
||||
uses: dorny/test-reporter@b082adf0eced0765477756c2a610396589b8c637 # v2.5.0
|
||||
uses: dorny/test-reporter@fe45e9537387dac839af0d33ba56eed8e24189e8 # v2.3.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.
|
||||
# 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()
|
||||
@@ -143,6 +143,4 @@ jobs:
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
|
||||
- name: Upload results to codecov.io
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
with:
|
||||
report_type: test_results
|
||||
uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
release_version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
10
.github/workflows/test.yml
vendored
10
.github/workflows/test.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
run: npm run test --coverage
|
||||
|
||||
- name: Report test results
|
||||
uses: dorny/test-reporter@b082adf0eced0765477756c2a610396589b8c637 # v2.5.0
|
||||
uses: dorny/test-reporter@fe45e9537387dac839af0d33ba56eed8e24189e8 # v2.3.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.
|
||||
# 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()
|
||||
@@ -67,6 +67,4 @@ jobs:
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
|
||||
- name: Upload results to codecov.io
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
with:
|
||||
report_type: test_results
|
||||
uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1
|
||||
|
||||
2
.github/workflows/version-bump.yml
vendored
2
.github/workflows/version-bump.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
permission-contents: write
|
||||
|
||||
- name: Checkout Branch
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
|
||||
156
ESM_MIGRATION_PLAN.md
Normal file
156
ESM_MIGRATION_PLAN.md
Normal file
@@ -0,0 +1,156 @@
|
||||
# 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 |
|
||||
10
angular.json
10
angular.json
@@ -18,17 +18,15 @@
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular/build:application",
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": {
|
||||
"base": "dist"
|
||||
},
|
||||
"outputPath": "dist",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"tsConfig": "tsconfig.json",
|
||||
"assets": [],
|
||||
"styles": [],
|
||||
"scripts": [],
|
||||
"browser": "src/main.ts"
|
||||
"scripts": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"output": "dist",
|
||||
"app": "build"
|
||||
},
|
||||
"afterSign": "scripts/notarize.mjs",
|
||||
"afterSign": "scripts/notarize.js",
|
||||
"mac": {
|
||||
"artifactName": "Bitwarden-Connector-${version}-mac.${ext}",
|
||||
"category": "public.app-category.productivity",
|
||||
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"win": {
|
||||
"target": ["portable", "nsis"],
|
||||
"sign": "scripts/sign.mjs"
|
||||
"sign": "scripts/sign.js"
|
||||
},
|
||||
"linux": {
|
||||
"category": "Utility",
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { pathsToModuleNameMapper } from "ts-jest";
|
||||
import tsconfig from "./tsconfig.json" with { type: "json" };
|
||||
const { pathsToModuleNameMapper } = require("ts-jest");
|
||||
const { compilerOptions } = require("./tsconfig");
|
||||
|
||||
import angularPresetsModule from "jest-preset-angular/presets/index.js";
|
||||
|
||||
const { defaultTransformerOptions } = angularPresetsModule;
|
||||
|
||||
const { compilerOptions } = tsconfig;
|
||||
const tsPreset = require("ts-jest/jest-preset");
|
||||
const angularPreset = require("jest-preset-angular/jest-preset");
|
||||
const { defaultTransformerOptions } = require("jest-preset-angular/presets");
|
||||
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
export default {
|
||||
module.exports = {
|
||||
// ...tsPreset,
|
||||
// ...angularPreset,
|
||||
preset: "jest-preset-angular",
|
||||
|
||||
reporters: ["default", "jest-junit"],
|
||||
@@ -24,13 +24,20 @@ export default {
|
||||
|
||||
roots: ["<rootDir>"],
|
||||
modulePaths: [compilerOptions.baseUrl],
|
||||
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: "<rootDir>/" }),
|
||||
moduleNameMapper: {
|
||||
...pathsToModuleNameMapper(compilerOptions.paths, { prefix: "<rootDir>/" }),
|
||||
// ESM compatibility: mock import.meta.url for tests
|
||||
"^(\\.{1,2}/.*)\\.js$": "$1",
|
||||
},
|
||||
setupFilesAfterEnv: ["<rootDir>/test.setup.ts"],
|
||||
// Workaround for a memory leak that crashes tests in CI:
|
||||
// https://github.com/facebook/jest/issues/9430#issuecomment-1149882002
|
||||
// Also anecdotally improves performance when run locally
|
||||
maxWorkers: 3,
|
||||
|
||||
// ESM support
|
||||
extensionsToTreatAsEsm: [".ts"],
|
||||
|
||||
transform: {
|
||||
"^.+\\.tsx?$": [
|
||||
"jest-preset-angular",
|
||||
@@ -43,6 +50,8 @@ export default {
|
||||
// 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
|
||||
isolatedModules: true,
|
||||
// ESM support
|
||||
useESM: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1,745 +0,0 @@
|
||||
# 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.
|
||||
28
jslib/angular/spec/test.ts
Normal file
28
jslib/angular/spec/test.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
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,
|
||||
});
|
||||
63
jslib/angular/src/components/environment.component.ts
Normal file
63
jslib/angular/src/components/environment.component.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
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,77 +1,75 @@
|
||||
import { animate, state, style, transition, trigger } from "@angular/animations";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, ModuleWithProviders, NgModule } from "@angular/core";
|
||||
import { DefaultNoComponentGlobalConfig, GlobalConfig, Toast, TOAST_CONFIG } from "ngx-toastr";
|
||||
import {
|
||||
DefaultNoComponentGlobalConfig,
|
||||
GlobalConfig,
|
||||
Toast as BaseToast,
|
||||
ToastPackage,
|
||||
ToastrService,
|
||||
TOAST_CONFIG,
|
||||
} from "ngx-toastr";
|
||||
|
||||
@Component({
|
||||
selector: "[toast-component2]",
|
||||
template: `
|
||||
@if (options().closeButton) {
|
||||
<button (click)="remove()" type="button" class="toast-close-button" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
*ngIf="options.closeButton"
|
||||
(click)="remove()"
|
||||
type="button"
|
||||
class="toast-close-button"
|
||||
aria-label="Close"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<div class="icon">
|
||||
<i></i>
|
||||
</div>
|
||||
<div>
|
||||
@if (title()) {
|
||||
<div [class]="options().titleClass" [attr.aria-label]="title()">
|
||||
{{ title() }}
|
||||
@if (duplicatesCount) {
|
||||
[{{ duplicatesCount + 1 }}]
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (message() && options().enableHtml) {
|
||||
<div
|
||||
role="alertdialog"
|
||||
aria-live="polite"
|
||||
[class]="options().messageClass"
|
||||
[innerHTML]="message()"
|
||||
></div>
|
||||
}
|
||||
@if (message() && !options().enableHtml) {
|
||||
<div
|
||||
role="alertdialog"
|
||||
aria-live="polite"
|
||||
[class]="options().messageClass"
|
||||
[attr.aria-label]="message()"
|
||||
>
|
||||
{{ message() }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (options().progressBar) {
|
||||
<div>
|
||||
<div class="toast-progress" [style.width]="width + '%'"></div>
|
||||
<div *ngIf="title" [class]="options.titleClass" [attr.aria-label]="title">
|
||||
{{ title }} <ng-container *ngIf="duplicatesCount">[{{ duplicatesCount + 1 }}]</ng-container>
|
||||
</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;
|
||||
}
|
||||
}
|
||||
<div
|
||||
*ngIf="message && options.enableHtml"
|
||||
role="alertdialog"
|
||||
aria-live="polite"
|
||||
[class]="options.messageClass"
|
||||
[innerHTML]="message"
|
||||
></div>
|
||||
<div
|
||||
*ngIf="message && !options.enableHtml"
|
||||
role="alertdialog"
|
||||
aria-live="polite"
|
||||
[class]="options.messageClass"
|
||||
[attr.aria-label]="message"
|
||||
>
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="options.progressBar">
|
||||
<div class="toast-progress" [style.width]="width + '%'"></div>
|
||||
</div>
|
||||
`,
|
||||
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,
|
||||
standalone: false,
|
||||
})
|
||||
export class BitwardenToast extends Toast {}
|
||||
export class BitwardenToast extends BaseToast {
|
||||
constructor(
|
||||
protected toastrService: ToastrService,
|
||||
public toastPackage: ToastPackage,
|
||||
) {
|
||||
super(toastrService, toastPackage);
|
||||
}
|
||||
}
|
||||
|
||||
export const BitwardenToastGlobalConfig: GlobalConfig = {
|
||||
...DefaultNoComponentGlobalConfig,
|
||||
|
||||
143
jslib/angular/src/services/jslib-services.module.ts
Normal file
143
jslib/angular/src/services/jslib-services.module.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
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 {}
|
||||
195
jslib/common/spec/domain/encString.spec.ts
Normal file
195
jslib/common/spec/domain/encString.spec.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
69
jslib/common/spec/domain/symmetricCryptoKey.spec.ts
Normal file
69
jslib/common/spec/domain/symmetricCryptoKey.spec.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
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.");
|
||||
});
|
||||
});
|
||||
});
|
||||
127
jslib/common/spec/misc/sequentialize.spec.ts
Normal file
127
jslib/common/spec/misc/sequentialize.spec.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
110
jslib/common/spec/misc/throttle.spec.ts
Normal file
110
jslib/common/spec/misc/throttle.spec.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
99
jslib/common/spec/services/consoleLog.service.spec.ts
Normal file
99
jslib/common/spec/services/consoleLog.service.spec.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
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({});
|
||||
});
|
||||
});
|
||||
84
jslib/common/spec/services/stateMigration.service.ts
Normal file
84
jslib/common/spec/services/stateMigration.service.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
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(),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
5
jslib/common/spec/test.ts
Normal file
5
jslib/common/spec/test.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { webcrypto } from "crypto";
|
||||
|
||||
Object.defineProperty(window, "crypto", {
|
||||
value: webcrypto,
|
||||
});
|
||||
@@ -1,3 +1,7 @@
|
||||
import { Substitute, Arg } from "@fluffy-spoon/substitute";
|
||||
|
||||
import { EncString } from "@/jslib/common/src/models/domain/encString";
|
||||
|
||||
function newGuid() {
|
||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
||||
const r = (Math.random() * 16) | 0;
|
||||
@@ -17,10 +21,17 @@ export function BuildTestObject<T, K extends keyof T = keyof 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) {
|
||||
const arr = new Uint8Array(length);
|
||||
for (let i = 0; i < length; i++) {
|
||||
arr[i] = start + i;
|
||||
}
|
||||
return arr.buffer;
|
||||
return arr;
|
||||
}
|
||||
|
||||
86
jslib/common/src/abstractions/crypto.service.ts
Normal file
86
jslib/common/src/abstractions/crypto.service.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
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,2 +1,34 @@
|
||||
// Stub file - re-exports DC EnvironmentService
|
||||
export { EnvironmentService, EnvironmentUrls } from "@/src/abstractions/environment.service";
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
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,2 +1,218 @@
|
||||
// Stub file - re-exports DC StateService
|
||||
export { StateService } from "@/src/abstractions/state.service";
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
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>;
|
||||
}
|
||||
|
||||
4
jslib/common/src/abstractions/stateMigration.service.ts
Normal file
4
jslib/common/src/abstractions/stateMigration.service.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export abstract class StateMigrationService {
|
||||
needsMigration: () => Promise<boolean>;
|
||||
migrate: () => Promise<void>;
|
||||
}
|
||||
@@ -1,2 +1,32 @@
|
||||
// Stub file - re-exports DC TokenService
|
||||
export { TokenService } from "@/src/abstractions/token.service";
|
||||
import { IdentityTokenResponse } from "../models/response/identityTokenResponse";
|
||||
|
||||
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>;
|
||||
}
|
||||
|
||||
6
jslib/common/src/enums/authenticationStatus.ts
Normal file
6
jslib/common/src/enums/authenticationStatus.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export enum AuthenticationStatus {
|
||||
Locked = "locked",
|
||||
Unlocked = "unlocked",
|
||||
LoggedOut = "loggedOut",
|
||||
Active = "active",
|
||||
}
|
||||
4
jslib/common/src/enums/cipherRepromptType.ts
Normal file
4
jslib/common/src/enums/cipherRepromptType.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum CipherRepromptType {
|
||||
None = 0,
|
||||
Password = 1,
|
||||
}
|
||||
6
jslib/common/src/enums/cipherType.ts
Normal file
6
jslib/common/src/enums/cipherType.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export enum CipherType {
|
||||
Login = 1,
|
||||
SecureNote = 2,
|
||||
Card = 3,
|
||||
Identity = 4,
|
||||
}
|
||||
7
jslib/common/src/enums/emergencyAccessStatusType.ts
Normal file
7
jslib/common/src/enums/emergencyAccessStatusType.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export enum EmergencyAccessStatusType {
|
||||
Invited = 0,
|
||||
Accepted = 1,
|
||||
Confirmed = 2,
|
||||
RecoveryInitiated = 3,
|
||||
RecoveryApproved = 4,
|
||||
}
|
||||
4
jslib/common/src/enums/emergencyAccessType.ts
Normal file
4
jslib/common/src/enums/emergencyAccessType.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum EmergencyAccessType {
|
||||
View = 0,
|
||||
Takeover = 1,
|
||||
}
|
||||
6
jslib/common/src/enums/fieldType.ts
Normal file
6
jslib/common/src/enums/fieldType.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export enum FieldType {
|
||||
Text = 0,
|
||||
Hidden = 1,
|
||||
Boolean = 2,
|
||||
Linked = 3,
|
||||
}
|
||||
4
jslib/common/src/enums/fileUploadType.ts
Normal file
4
jslib/common/src/enums/fileUploadType.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum FileUploadType {
|
||||
Direct = 0,
|
||||
Azure = 1,
|
||||
}
|
||||
4
jslib/common/src/enums/hashPurpose.ts
Normal file
4
jslib/common/src/enums/hashPurpose.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum HashPurpose {
|
||||
ServerAuthorization = 1,
|
||||
LocalAuthorization = 2,
|
||||
}
|
||||
4
jslib/common/src/enums/keySuffixOptions.ts
Normal file
4
jslib/common/src/enums/keySuffixOptions.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum KeySuffixOptions {
|
||||
Auto = "auto",
|
||||
Biometric = "biometric",
|
||||
}
|
||||
20
jslib/common/src/enums/notificationType.ts
Normal file
20
jslib/common/src/enums/notificationType.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
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,
|
||||
}
|
||||
5
jslib/common/src/enums/organizationUserStatusType.ts
Normal file
5
jslib/common/src/enums/organizationUserStatusType.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum OrganizationUserStatusType {
|
||||
Invited = 0,
|
||||
Accepted = 1,
|
||||
Confirmed = 2,
|
||||
}
|
||||
7
jslib/common/src/enums/organizationUserType.ts
Normal file
7
jslib/common/src/enums/organizationUserType.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export enum OrganizationUserType {
|
||||
Owner = 0,
|
||||
Admin = 1,
|
||||
User = 2,
|
||||
Manager = 3,
|
||||
Custom = 4,
|
||||
}
|
||||
11
jslib/common/src/enums/paymentMethodType.ts
Normal file
11
jslib/common/src/enums/paymentMethodType.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export enum PaymentMethodType {
|
||||
Card = 0,
|
||||
BankAccount = 1,
|
||||
PayPal = 2,
|
||||
BitPay = 3,
|
||||
Credit = 4,
|
||||
WireTransfer = 5,
|
||||
AppleInApp = 6,
|
||||
GoogleInApp = 7,
|
||||
Check = 8,
|
||||
}
|
||||
27
jslib/common/src/enums/permissions.ts
Normal file
27
jslib/common/src/enums/permissions.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
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,
|
||||
}
|
||||
3
jslib/common/src/enums/planSponsorshipType.ts
Normal file
3
jslib/common/src/enums/planSponsorshipType.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export enum PlanSponsorshipType {
|
||||
FamiliesForEnterprise = 0,
|
||||
}
|
||||
14
jslib/common/src/enums/planType.ts
Normal file
14
jslib/common/src/enums/planType.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
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,
|
||||
}
|
||||
6
jslib/common/src/enums/productType.ts
Normal file
6
jslib/common/src/enums/productType.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export enum ProductType {
|
||||
Free = 0,
|
||||
Families = 1,
|
||||
Teams = 2,
|
||||
Enterprise = 3,
|
||||
}
|
||||
5
jslib/common/src/enums/providerUserStatusType.ts
Normal file
5
jslib/common/src/enums/providerUserStatusType.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum ProviderUserStatusType {
|
||||
Invited = 0,
|
||||
Accepted = 1,
|
||||
Confirmed = 2,
|
||||
}
|
||||
4
jslib/common/src/enums/providerUserType.ts
Normal file
4
jslib/common/src/enums/providerUserType.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum ProviderUserType {
|
||||
ProviderAdmin = 0,
|
||||
ServiceUser = 1,
|
||||
}
|
||||
33
jslib/common/src/enums/ssoEnums.ts
Normal file
33
jslib/common/src/enums/ssoEnums.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
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,6 +3,5 @@ export enum StateVersion {
|
||||
Two = 2, // Move to a typed State object
|
||||
Three = 3, // Fix migration of users' premium status
|
||||
Four = 4, // Fix 'Never Lock' option by removing stale data
|
||||
Five = 5, // New state service implementation
|
||||
Latest = Five,
|
||||
Latest = Four,
|
||||
}
|
||||
|
||||
7
jslib/common/src/enums/transactionType.ts
Normal file
7
jslib/common/src/enums/transactionType.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export enum TransactionType {
|
||||
Charge = 0,
|
||||
Credit = 1,
|
||||
PromotionalCredit = 2,
|
||||
ReferralCredit = 3,
|
||||
Refund = 4,
|
||||
}
|
||||
8
jslib/common/src/enums/uriMatchType.ts
Normal file
8
jslib/common/src/enums/uriMatchType.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export enum UriMatchType {
|
||||
Domain = 0,
|
||||
Host = 1,
|
||||
StartsWith = 2,
|
||||
Exact = 3,
|
||||
RegularExpression = 4,
|
||||
Never = 5,
|
||||
}
|
||||
13
jslib/common/src/factories/accountFactory.ts
Normal file
13
jslib/common/src/factories/accountFactory.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
13
jslib/common/src/factories/globalStateFactory.ts
Normal file
13
jslib/common/src/factories/globalStateFactory.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
29
jslib/common/src/factories/stateFactory.ts
Normal file
29
jslib/common/src/factories/stateFactory.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
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,4 +26,9 @@ export class NodeUtils {
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
72
jslib/common/src/misc/serviceUtils.ts
Normal file
72
jslib/common/src/misc/serviceUtils.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
69
jslib/common/src/misc/throttle.ts
Normal file
69
jslib/common/src/misc/throttle.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
static fromB64ToArray(str: string): Uint8Array<ArrayBuffer> {
|
||||
static fromB64ToArray(str: string): Uint8Array {
|
||||
if (Utils.isNode) {
|
||||
return new Uint8Array(Buffer.from(str, "base64"));
|
||||
} else {
|
||||
@@ -49,11 +49,11 @@ export class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
static fromUrlB64ToArray(str: string): Uint8Array<ArrayBuffer> {
|
||||
static fromUrlB64ToArray(str: string): Uint8Array {
|
||||
return Utils.fromB64ToArray(Utils.fromUrlB64ToB64(str));
|
||||
}
|
||||
|
||||
static fromHexToArray(str: string): Uint8Array<ArrayBuffer> {
|
||||
static fromHexToArray(str: string): Uint8Array {
|
||||
if (Utils.isNode) {
|
||||
return new Uint8Array(Buffer.from(str, "hex"));
|
||||
} else {
|
||||
@@ -65,7 +65,7 @@ export class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
static fromUtf8ToArray(str: string): Uint8Array<ArrayBuffer> {
|
||||
static fromUtf8ToArray(str: string): Uint8Array {
|
||||
if (Utils.isNode) {
|
||||
return new Uint8Array(Buffer.from(str, "utf8"));
|
||||
} else {
|
||||
@@ -78,7 +78,7 @@ export class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
static fromByteStringToArray(str: string): Uint8Array<ArrayBuffer> {
|
||||
static fromByteStringToArray(str: string): Uint8Array {
|
||||
const arr = new Uint8Array(str.length);
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
arr[i] = str.charCodeAt(i);
|
||||
@@ -99,8 +99,8 @@ export class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
static fromBufferToUrlB64(buffer: Uint8Array<ArrayBuffer>): string {
|
||||
return Utils.fromB64toUrlB64(Utils.fromBufferToB64(buffer.buffer));
|
||||
static fromBufferToUrlB64(buffer: ArrayBuffer): string {
|
||||
return Utils.fromB64toUrlB64(Utils.fromBufferToB64(buffer));
|
||||
}
|
||||
|
||||
static fromB64toUrlB64(b64Str: string) {
|
||||
|
||||
7779
jslib/common/src/misc/wordlist.ts
Normal file
7779
jslib/common/src/misc/wordlist.ts
Normal file
File diff suppressed because it is too large
Load Diff
55
jslib/common/src/models/api/permissionsApi.ts
Normal file
55
jslib/common/src/models/api/permissionsApi.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
136
jslib/common/src/models/api/ssoConfigApi.ts
Normal file
136
jslib/common/src/models/api/ssoConfigApi.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
78
jslib/common/src/models/data/organizationData.ts
Normal file
78
jslib/common/src/models/data/organizationData.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
23
jslib/common/src/models/data/providerData.ts
Normal file
23
jslib/common/src/models/data/providerData.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
151
jslib/common/src/models/domain/account.ts
Normal file
151
jslib/common/src/models/domain/account.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
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,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
82
jslib/common/src/models/domain/domainBase.ts
Normal file
82
jslib/common/src/models/domain/domainBase.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
3
jslib/common/src/models/domain/encArrayBuffer.ts
Normal file
3
jslib/common/src/models/domain/encArrayBuffer.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export class EncArrayBuffer {
|
||||
constructor(public buffer: ArrayBuffer) {}
|
||||
}
|
||||
122
jslib/common/src/models/domain/encString.ts
Normal file
122
jslib/common/src/models/domain/encString.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
8
jslib/common/src/models/domain/encryptedObject.ts
Normal file
8
jslib/common/src/models/domain/encryptedObject.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { SymmetricCryptoKey } from "./symmetricCryptoKey";
|
||||
|
||||
export class EncryptedObject {
|
||||
iv: ArrayBuffer;
|
||||
data: ArrayBuffer;
|
||||
mac: ArrayBuffer;
|
||||
key: SymmetricCryptoKey;
|
||||
}
|
||||
@@ -2,5 +2,9 @@ export class EnvironmentUrls {
|
||||
base: string = null;
|
||||
api: string = null;
|
||||
identity: string = null;
|
||||
icons: string = null;
|
||||
notifications: string = null;
|
||||
events: string = null;
|
||||
webVault: string = null;
|
||||
keyConnector: string = null;
|
||||
}
|
||||
|
||||
40
jslib/common/src/models/domain/globalState.ts
Normal file
40
jslib/common/src/models/domain/globalState.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
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;
|
||||
}
|
||||
184
jslib/common/src/models/domain/organization.ts
Normal file
184
jslib/common/src/models/domain/organization.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
50
jslib/common/src/models/domain/provider.ts
Normal file
50
jslib/common/src/models/domain/provider.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
17
jslib/common/src/models/domain/state.ts
Normal file
17
jslib/common/src/models/domain/state.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
16
jslib/common/src/models/domain/treeNode.ts
Normal file
16
jslib/common/src/models/domain/treeNode.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
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;
|
||||
}
|
||||
10
jslib/common/src/models/response/apiKeyResponse.ts
Normal file
10
jslib/common/src/models/response/apiKeyResponse.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { BaseResponse } from "./baseResponse";
|
||||
|
||||
export class ApiKeyResponse extends BaseResponse {
|
||||
apiKey: string;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.apiKey = this.getResponseProperty("ApiKey");
|
||||
}
|
||||
}
|
||||
83
jslib/common/src/models/response/billingResponse.ts
Normal file
83
jslib/common/src/models/response/billingResponse.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
32
jslib/common/src/models/response/breachAccountResponse.ts
Normal file
32
jslib/common/src/models/response/breachAccountResponse.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
20
jslib/common/src/models/response/deviceResponse.ts
Normal file
20
jslib/common/src/models/response/deviceResponse.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
20
jslib/common/src/models/response/domainsResponse.ts
Normal file
20
jslib/common/src/models/response/domainsResponse.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
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 = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
14
jslib/common/src/models/response/globalDomainResponse.ts
Normal file
14
jslib/common/src/models/response/globalDomainResponse.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
31
jslib/common/src/models/response/groupResponse.ts
Normal file
31
jslib/common/src/models/response/groupResponse.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { BaseResponse } from "./baseResponse";
|
||||
|
||||
export class KeyConnectorUserKeyResponse extends BaseResponse {
|
||||
key: string;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.key = this.getResponseProperty("Key");
|
||||
}
|
||||
}
|
||||
12
jslib/common/src/models/response/keysResponse.ts
Normal file
12
jslib/common/src/models/response/keysResponse.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
13
jslib/common/src/models/response/listResponse.ts
Normal file
13
jslib/common/src/models/response/listResponse.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
98
jslib/common/src/models/response/notificationResponse.ts
Normal file
98
jslib/common/src/models/response/notificationResponse.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
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"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { KeysResponse } from "./keysResponse";
|
||||
|
||||
export class OrganizationKeysResponse extends KeysResponse {
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
}
|
||||
}
|
||||
60
jslib/common/src/models/response/organizationResponse.ts
Normal file
60
jslib/common/src/models/response/organizationResponse.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
70
jslib/common/src/models/response/organizationUserResponse.ts
Normal file
70
jslib/common/src/models/response/organizationUserResponse.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
18
jslib/common/src/models/response/paymentResponse.ts
Normal file
18
jslib/common/src/models/response/paymentResponse.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { BaseResponse } from "./baseResponse";
|
||||
import { ProfileResponse } from "./profileResponse";
|
||||
|
||||
export class PaymentResponse extends BaseResponse {
|
||||
userProfile: ProfileResponse;
|
||||
paymentIntentClientSecret: string;
|
||||
success: boolean;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
const userProfile = this.getResponseProperty("UserProfile");
|
||||
if (userProfile != null) {
|
||||
this.userProfile = new ProfileResponse(userProfile);
|
||||
}
|
||||
this.paymentIntentClientSecret = this.getResponseProperty("PaymentIntentClientSecret");
|
||||
this.success = this.getResponseProperty("Success");
|
||||
}
|
||||
}
|
||||
95
jslib/common/src/models/response/planResponse.ts
Normal file
95
jslib/common/src/models/response/planResponse.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { PlanType } from "../../enums/planType";
|
||||
import { ProductType } from "../../enums/productType";
|
||||
|
||||
import { BaseResponse } from "./baseResponse";
|
||||
|
||||
export class PlanResponse extends BaseResponse {
|
||||
type: PlanType;
|
||||
product: ProductType;
|
||||
name: string;
|
||||
isAnnual: boolean;
|
||||
nameLocalizationKey: string;
|
||||
descriptionLocalizationKey: string;
|
||||
canBeUsedByBusiness: boolean;
|
||||
baseSeats: number;
|
||||
baseStorageGb: number;
|
||||
maxCollections: number;
|
||||
maxUsers: number;
|
||||
|
||||
hasAdditionalSeatsOption: boolean;
|
||||
maxAdditionalSeats: number;
|
||||
hasAdditionalStorageOption: boolean;
|
||||
maxAdditionalStorage: number;
|
||||
hasPremiumAccessOption: boolean;
|
||||
trialPeriodDays: number;
|
||||
|
||||
hasSelfHost: boolean;
|
||||
hasPolicies: boolean;
|
||||
hasGroups: boolean;
|
||||
hasDirectory: boolean;
|
||||
hasEvents: boolean;
|
||||
hasTotp: boolean;
|
||||
has2fa: boolean;
|
||||
hasApi: boolean;
|
||||
hasSso: boolean;
|
||||
hasResetPassword: boolean;
|
||||
usersGetPremium: boolean;
|
||||
|
||||
upgradeSortOrder: number;
|
||||
displaySortOrder: number;
|
||||
legacyYear: number;
|
||||
disabled: boolean;
|
||||
|
||||
stripePlanId: string;
|
||||
stripeSeatPlanId: string;
|
||||
stripeStoragePlanId: string;
|
||||
stripePremiumAccessPlanId: string;
|
||||
basePrice: number;
|
||||
seatPrice: number;
|
||||
additionalStoragePricePerGb: number;
|
||||
premiumAccessOptionPrice: number;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.type = this.getResponseProperty("Type");
|
||||
this.product = this.getResponseProperty("Product");
|
||||
this.name = this.getResponseProperty("Name");
|
||||
this.isAnnual = this.getResponseProperty("IsAnnual");
|
||||
this.nameLocalizationKey = this.getResponseProperty("NameLocalizationKey");
|
||||
this.descriptionLocalizationKey = this.getResponseProperty("DescriptionLocalizationKey");
|
||||
this.canBeUsedByBusiness = this.getResponseProperty("CanBeUsedByBusiness");
|
||||
this.baseSeats = this.getResponseProperty("BaseSeats");
|
||||
this.baseStorageGb = this.getResponseProperty("BaseStorageGb");
|
||||
this.maxCollections = this.getResponseProperty("MaxCollections");
|
||||
this.maxUsers = this.getResponseProperty("MaxUsers");
|
||||
this.hasAdditionalSeatsOption = this.getResponseProperty("HasAdditionalSeatsOption");
|
||||
this.maxAdditionalSeats = this.getResponseProperty("MaxAdditionalSeats");
|
||||
this.hasAdditionalStorageOption = this.getResponseProperty("HasAdditionalStorageOption");
|
||||
this.maxAdditionalStorage = this.getResponseProperty("MaxAdditionalStorage");
|
||||
this.hasPremiumAccessOption = this.getResponseProperty("HasPremiumAccessOption");
|
||||
this.trialPeriodDays = this.getResponseProperty("TrialPeriodDays");
|
||||
this.hasSelfHost = this.getResponseProperty("HasSelfHost");
|
||||
this.hasPolicies = this.getResponseProperty("HasPolicies");
|
||||
this.hasGroups = this.getResponseProperty("HasGroups");
|
||||
this.hasDirectory = this.getResponseProperty("HasDirectory");
|
||||
this.hasEvents = this.getResponseProperty("HasEvents");
|
||||
this.hasTotp = this.getResponseProperty("HasTotp");
|
||||
this.has2fa = this.getResponseProperty("Has2fa");
|
||||
this.hasApi = this.getResponseProperty("HasApi");
|
||||
this.hasSso = this.getResponseProperty("HasSso");
|
||||
this.hasResetPassword = this.getResponseProperty("HasResetPassword");
|
||||
this.usersGetPremium = this.getResponseProperty("UsersGetPremium");
|
||||
this.upgradeSortOrder = this.getResponseProperty("UpgradeSortOrder");
|
||||
this.displaySortOrder = this.getResponseProperty("SortOrder");
|
||||
this.legacyYear = this.getResponseProperty("LegacyYear");
|
||||
this.disabled = this.getResponseProperty("Disabled");
|
||||
this.stripePlanId = this.getResponseProperty("StripePlanId");
|
||||
this.stripeSeatPlanId = this.getResponseProperty("StripeSeatPlanId");
|
||||
this.stripeStoragePlanId = this.getResponseProperty("StripeStoragePlanId");
|
||||
this.stripePremiumAccessPlanId = this.getResponseProperty("StripePremiumAccessPlanId");
|
||||
this.basePrice = this.getResponseProperty("BasePrice");
|
||||
this.seatPrice = this.getResponseProperty("SeatPrice");
|
||||
this.additionalStoragePricePerGb = this.getResponseProperty("AdditionalStoragePricePerGb");
|
||||
this.premiumAccessOptionPrice = this.getResponseProperty("PremiumAccessOptionPrice");
|
||||
}
|
||||
}
|
||||
14
jslib/common/src/models/response/preloginResponse.ts
Normal file
14
jslib/common/src/models/response/preloginResponse.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { KdfType } from "../../enums/kdfType";
|
||||
|
||||
import { BaseResponse } from "./baseResponse";
|
||||
|
||||
export class PreloginResponse extends BaseResponse {
|
||||
kdf: KdfType;
|
||||
kdfIterations: number;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.kdf = this.getResponseProperty("Kdf");
|
||||
this.kdfIterations = this.getResponseProperty("KdfIterations");
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user