1
0
mirror of https://github.com/bitwarden/jslib synced 2025-12-10 13:23:15 +00:00

Apply prettier

This commit is contained in:
Hinton
2022-01-03 12:49:00 +01:00
parent d68d4b4bdc
commit c3aaf33ce1
586 changed files with 48808 additions and 43106 deletions

View File

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

View File

@@ -20,7 +20,6 @@ jobs:
- name: Print lines of code
run: cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git
build:
name: Build jslib
runs-on: ${{ matrix.os }}
@@ -33,7 +32,7 @@ jobs:
- name: Set up Node
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
node-version: '16'
node-version: "16"
- name: Install node-gyp
run: |
@@ -106,7 +105,7 @@ jobs:
secrets: "devops-alerts-slack-webhook-url"
- name: Notify Slack on failure
uses: act10ns/slack@e4e71685b9b239384b0f676a63c32367f59c2522 # v1.2.2
uses: act10ns/slack@e4e71685b9b239384b0f676a63c32367f59c2522 # v1.2.2
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}

32
.vscode/launch.json vendored
View File

@@ -1,18 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Jasmine Individual Test",
"program": "${workspaceRoot}\\node_modules\\jasmine\\bin\\jasmine.js",
"preLaunchTask": "npm run build",
"args": [
"${workspaceFolder}/dist\\spec\\node\\services\\nodeCryptoFunction.service.spec.js"
]
}
]
}
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Jasmine Individual Test",
"program": "${workspaceRoot}\\node_modules\\jasmine\\bin\\jasmine.js",
"preLaunchTask": "npm run build",
"args": ["${workspaceFolder}/dist\\spec\\node\\services\\nodeCryptoFunction.service.spec.js"]
}
]
}

22
.vscode/tasks.json vendored
View File

@@ -1,12 +1,12 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "npm run build",
"type": "shell",
"command": "npm run build"
}
]
}
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "npm run build",
"type": "shell",
"command": "npm run build"
}
]
}

View File

@@ -6,15 +6,11 @@ Please visit our [Community Forums](https://community.bitwarden.com/) for genera
Here is how you can get involved:
* **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one
* **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code
* **Report a bug or submit a bugfix:** Use Github issues and pull requests
* **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
* **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
- **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one
- **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code
- **Report a bug or submit a bugfix:** Use Github issues and pull requests
- **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
- **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
## Contributor Agreement
@@ -22,9 +18,9 @@ Please sign the [Contributor Agreement](https://cla-assistant.io/bitwarden/jslib
## Pull Request Guidelines
* use `npm run lint` and fix any linting suggestions before submitting a pull request
* commit any pull requests against the `master` branch
* include a link to your Community Forums post
- use `npm run lint` and fix any linting suggestions before submitting a pull request
- commit any pull requests against the `master` branch
- include a link to your Community Forums post
# Introduction to jslib and git submodules
@@ -33,36 +29,39 @@ jslib is a repository that contains shared code for all Bitwarden Typescript/Jav
If you haven't worked with submodules before, you should start by reading some basic guides (such as the [git scm chapter](https://git-scm.com/book/en/v2/Git-Tools-Submodules) or the [Atlassian tutorial](https://www.atlassian.com/git/tutorials/git-submodule)).
# Setting up your Local Dev environment for jslib
In order to easily develop local changes to jslib across each of the TypeScript/JavaScript clients, we recommend using symlinks for the submodule so that you only have to make the change once for it to be reflected across all your local repos.
## Prerequisites
1. git bash or other git command line
2. In order for this to work well, you need to use a consistent relative directory structure. Repos should be cloned in the following way:
* `./<your project(s) directory>`; we'll call this `/dev` ('cause why not)
* jslib - `git clone https://github.com/bitwarden/jslib.git` (/dev/jslib)
* web - `git clone --recurse-submodules https://github.com/bitwarden/web.git` (/dev/web)
* desktop - `git clone --recurse-submodules https://github.com/bitwarden/desktop.git` (/dev/desktop)
* browser - `git clone --recurse-submodules https://github.com/bitwarden/browser.git` (/dev/browser)
* cli - `git clone --recurse-submodules https://github.com/bitwarden/cli` (/dev/cli)
- `./<your project(s) directory>`; we'll call this `/dev` ('cause why not)
- jslib - `git clone https://github.com/bitwarden/jslib.git` (/dev/jslib)
- web - `git clone --recurse-submodules https://github.com/bitwarden/web.git` (/dev/web)
- desktop - `git clone --recurse-submodules https://github.com/bitwarden/desktop.git` (/dev/desktop)
- browser - `git clone --recurse-submodules https://github.com/bitwarden/browser.git` (/dev/browser)
- cli - `git clone --recurse-submodules https://github.com/bitwarden/cli` (/dev/cli)
You should notice web, desktop, browser and cli each reference jslib as a git submodule. If you've already cloned the repos but didn't use `--recurse-submodules` then you'll need to init the submodule with `npm run sub:init`.
You should notice web, desktop, browser and cli each reference jslib as a git submodule. If you've already cloned the repos but didn't use `--recurse-submodules` then you'll need to init the submodule with `npm run sub:init`.
## Configure Symlinks
Using `git clone` will make symlinks added to your repo be seen by git as plain text file paths. We need to prevent that. In the project root run, `git config core.symlinks true`.
For each project other than jslib, run the following:
* For macOS/Linux: `npm run symlink:mac`
* For Windows: `npm run symlink:win`
- For macOS/Linux: `npm run symlink:mac`
- For Windows: `npm run symlink:win`
Your client repos will now be pointing to your local jslib repo. You can now make changes in jslib and they will be immediately shared by the clients (just like they will be in production).
## Committing and pushing jslib changes
* You work on jslib like any other repo. Check out a new branch, make some commits, and push to remote when you're ready to submit a PR.
* Do not commit your jslib changes in the client repo. Your changes to the client and your changes to jslib should stay completely separate.
* When submitting a client PR that depends on a jslib PR, please include a link to the jslib PR so that the reviewer knows there are jslib changes.
- You work on jslib like any other repo. Check out a new branch, make some commits, and push to remote when you're ready to submit a PR.
- Do not commit your jslib changes in the client repo. Your changes to the client and your changes to jslib should stay completely separate.
- When submitting a client PR that depends on a jslib PR, please include a link to the jslib PR so that the reviewer knows there are jslib changes.
### Updating jslib on a feature branch
@@ -70,8 +69,8 @@ If you've submitted a client PR and a jslib PR, your jslib PR will be approved a
1. If you've symlinked the client's jslib directory following the steps above, you'll need to delete that symlink and then run `npm run sub:init`.
2. Update the jslib submodule:
* if you're working on your own fork, run `git submodule update --remote --reference upstream`.
* if you're working on a branch on the official repo, run `npm run sub:update`
- if you're working on your own fork, run `git submodule update --remote --reference upstream`.
- if you're working on a branch on the official repo, run `npm run sub:update`
3. To check you've done this correctly, you can `cd` into your jslib directory and run `git log`. You should see your recent changes in the log. This will also show you the most recent commit hash, which should match the most recent commit hash on [Github](https://github.com/bitwarden/jslib).
4. Add your changes: `git add jslib`
5. Commit your changes: `git commit -m "update jslib version"`
@@ -89,7 +88,8 @@ If you've made changes to jslib without needing to make any changes to the clien
4. Create a new PR to the client repo. Please include a link to your jslib PR so that reviewers know why you're updating jslib.
## Merge Conflicts
At times when you need to perform a `git merge master` into your feature or local branch, and there are conflicting version references to the *jslib* repo from your other clients, you will not be able to use the traditional merge or stage functions you would normally use for a file.
At times when you need to perform a `git merge master` into your feature or local branch, and there are conflicting version references to the _jslib_ repo from your other clients, you will not be able to use the traditional merge or stage functions you would normally use for a file.
To resolve you must use either `git reset` or update the index directly using `git update-index`. You can use (depending on whether you have symlink'd jslib) one of the following:

View File

@@ -5,13 +5,14 @@
Common code referenced across Bitwarden JavaScript projects.
## Requirements
* [Node.js](https://nodejs.org) v16.13.1 or greater
* NPM v8
* Git
* node-gyp
- [Node.js](https://nodejs.org) v16.13.1 or greater
- NPM v8
- Git
- node-gyp
### Windows
* *Microsoft Build Tools 2015* in Visual Studio Installer
* [Windows 10 SDK 17134](https://developer.microsoft.com/en-us/windows/downloads/sdk-archive/)
either by downloading it seperately or through the Visual Studio Installer.
- _Microsoft Build Tools 2015_ in Visual Studio Installer
- [Windows 10 SDK 17134](https://developer.microsoft.com/en-us/windows/downloads/sdk-archive/)
either by downloading it seperately or through the Visual Studio Installer.

View File

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

View File

@@ -1,124 +1,120 @@
import {
Directive,
Input,
OnChanges,
SimpleChanges,
} from '@angular/core';
import { Directive, Input, OnChanges, SimpleChanges } from "@angular/core";
import {
CdkDragDrop,
moveItemInArray,
} from '@angular/cdk/drag-drop';
import { CdkDragDrop, moveItemInArray } from "@angular/cdk/drag-drop";
import { EventService } from 'jslib-common/abstractions/event.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { EventService } from "jslib-common/abstractions/event.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { CipherView } from 'jslib-common/models/view/cipherView';
import { FieldView } from 'jslib-common/models/view/fieldView';
import { CipherView } from "jslib-common/models/view/cipherView";
import { FieldView } from "jslib-common/models/view/fieldView";
import { CipherType } from 'jslib-common/enums/cipherType';
import { EventType } from 'jslib-common/enums/eventType';
import { FieldType } from 'jslib-common/enums/fieldType';
import { CipherType } from "jslib-common/enums/cipherType";
import { EventType } from "jslib-common/enums/eventType";
import { FieldType } from "jslib-common/enums/fieldType";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
@Directive()
export class AddEditCustomFieldsComponent implements OnChanges {
@Input() cipher: CipherView;
@Input() thisCipherType: CipherType;
@Input() editMode: boolean;
@Input() cipher: CipherView;
@Input() thisCipherType: CipherType;
@Input() editMode: boolean;
addFieldType: FieldType = FieldType.Text;
addFieldTypeOptions: any[];
addFieldLinkedTypeOption: any;
linkedFieldOptions: any[] = [];
addFieldType: FieldType = FieldType.Text;
addFieldTypeOptions: any[];
addFieldLinkedTypeOption: any;
linkedFieldOptions: any[] = [];
cipherType = CipherType;
fieldType = FieldType;
eventType = EventType;
cipherType = CipherType;
fieldType = FieldType;
eventType = EventType;
constructor(private i18nService: I18nService, private eventService: EventService) {
this.addFieldTypeOptions = [
{ name: i18nService.t('cfTypeText'), value: FieldType.Text },
{ name: i18nService.t('cfTypeHidden'), value: FieldType.Hidden },
{ name: i18nService.t('cfTypeBoolean'), value: FieldType.Boolean },
];
this.addFieldLinkedTypeOption = { name: this.i18nService.t('cfTypeLinked'), value: FieldType.Linked };
constructor(private i18nService: I18nService, private eventService: EventService) {
this.addFieldTypeOptions = [
{ name: i18nService.t("cfTypeText"), value: FieldType.Text },
{ name: i18nService.t("cfTypeHidden"), value: FieldType.Hidden },
{ name: i18nService.t("cfTypeBoolean"), value: FieldType.Boolean },
];
this.addFieldLinkedTypeOption = {
name: this.i18nService.t("cfTypeLinked"),
value: FieldType.Linked,
};
}
ngOnChanges(changes: SimpleChanges) {
if (changes.thisCipherType != null) {
this.setLinkedFieldOptions();
if (!changes.thisCipherType.firstChange) {
this.resetCipherLinkedFields();
}
}
}
addField() {
if (this.cipher.fields == null) {
this.cipher.fields = [];
}
ngOnChanges(changes: SimpleChanges) {
if (changes.thisCipherType != null) {
this.setLinkedFieldOptions();
const f = new FieldView();
f.type = this.addFieldType;
f.newField = true;
if (!changes.thisCipherType.firstChange) {
this.resetCipherLinkedFields();
}
}
if (f.type === FieldType.Linked) {
f.linkedId = this.linkedFieldOptions[0].value;
}
addField() {
if (this.cipher.fields == null) {
this.cipher.fields = [];
}
this.cipher.fields.push(f);
}
const f = new FieldView();
f.type = this.addFieldType;
f.newField = true;
removeField(field: FieldView) {
const i = this.cipher.fields.indexOf(field);
if (i > -1) {
this.cipher.fields.splice(i, 1);
}
}
if (f.type === FieldType.Linked) {
f.linkedId = this.linkedFieldOptions[0].value;
}
toggleFieldValue(field: FieldView) {
const f = field as any;
f.showValue = !f.showValue;
if (this.editMode && f.showValue) {
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipher.id);
}
}
this.cipher.fields.push(f);
trackByFunction(index: number, item: any) {
return index;
}
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.cipher.fields, event.previousIndex, event.currentIndex);
}
private setLinkedFieldOptions() {
if (this.cipher.linkedFieldOptions == null) {
return;
}
removeField(field: FieldView) {
const i = this.cipher.fields.indexOf(field);
if (i > -1) {
this.cipher.fields.splice(i, 1);
}
const options: any = [];
this.cipher.linkedFieldOptions.forEach((linkedFieldOption, id) =>
options.push({ name: this.i18nService.t(linkedFieldOption.i18nKey), value: id })
);
this.linkedFieldOptions = options.sort(Utils.getSortFunction(this.i18nService, "name"));
}
private resetCipherLinkedFields() {
if (this.cipher.fields == null || this.cipher.fields.length === 0) {
return;
}
toggleFieldValue(field: FieldView) {
const f = (field as any);
f.showValue = !f.showValue;
if (this.editMode && f.showValue) {
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipher.id);
}
// Delete any Linked custom fields if the item type does not support them
if (this.cipher.linkedFieldOptions == null) {
this.cipher.fields = this.cipher.fields.filter((f) => f.type !== FieldType.Linked);
return;
}
trackByFunction(index: number, item: any) {
return index;
}
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.cipher.fields, event.previousIndex, event.currentIndex);
}
private setLinkedFieldOptions() {
if (this.cipher.linkedFieldOptions == null) {
return;
}
const options: any = [];
this.cipher.linkedFieldOptions.forEach((linkedFieldOption, id) =>
options.push({ name: this.i18nService.t(linkedFieldOption.i18nKey), value: id }));
this.linkedFieldOptions = options.sort(Utils.getSortFunction(this.i18nService, 'name'));
}
private resetCipherLinkedFields() {
if (this.cipher.fields == null || this.cipher.fields.length === 0) {
return;
}
// Delete any Linked custom fields if the item type does not support them
if (this.cipher.linkedFieldOptions == null) {
this.cipher.fields = this.cipher.fields.filter(f => f.type !== FieldType.Linked);
return;
}
this.cipher.fields
.filter(f => f.type === FieldType.Linked)
.forEach(f => f.linkedId = this.linkedFieldOptions[0].value);
}
this.cipher.fields
.filter((f) => f.type === FieldType.Linked)
.forEach((f) => (f.linkedId = this.linkedFieldOptions[0].value));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,250 +1,291 @@
import {
Directive,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { Cipher } from 'jslib-common/models/domain/cipher';
import { ErrorResponse } from 'jslib-common/models/response/errorResponse';
import { Cipher } from "jslib-common/models/domain/cipher";
import { ErrorResponse } from "jslib-common/models/response/errorResponse";
import { AttachmentView } from 'jslib-common/models/view/attachmentView';
import { CipherView } from 'jslib-common/models/view/cipherView';
import { AttachmentView } from "jslib-common/models/view/attachmentView";
import { CipherView } from "jslib-common/models/view/cipherView";
@Directive()
export class AttachmentsComponent implements OnInit {
@Input() cipherId: string;
@Output() onUploadedAttachment = new EventEmitter();
@Output() onDeletedAttachment = new EventEmitter();
@Output() onReuploadedAttachment = new EventEmitter();
@Input() cipherId: string;
@Output() onUploadedAttachment = new EventEmitter();
@Output() onDeletedAttachment = new EventEmitter();
@Output() onReuploadedAttachment = new EventEmitter();
cipher: CipherView;
cipherDomain: Cipher;
hasUpdatedKey: boolean;
canAccessAttachments: boolean;
formPromise: Promise<any>;
deletePromises: { [id: string]: Promise<any>; } = {};
reuploadPromises: { [id: string]: Promise<any>; } = {};
emergencyAccessId?: string = null;
cipher: CipherView;
cipherDomain: Cipher;
hasUpdatedKey: boolean;
canAccessAttachments: boolean;
formPromise: Promise<any>;
deletePromises: { [id: string]: Promise<any> } = {};
reuploadPromises: { [id: string]: Promise<any> } = {};
emergencyAccessId?: string = null;
constructor(protected cipherService: CipherService, protected i18nService: I18nService,
protected cryptoService: CryptoService, protected platformUtilsService: PlatformUtilsService,
protected apiService: ApiService, protected win: Window,
protected logService: LogService, protected stateService: StateService) { }
constructor(
protected cipherService: CipherService,
protected i18nService: I18nService,
protected cryptoService: CryptoService,
protected platformUtilsService: PlatformUtilsService,
protected apiService: ApiService,
protected win: Window,
protected logService: LogService,
protected stateService: StateService
) {}
async ngOnInit() {
await this.init();
async ngOnInit() {
await this.init();
}
async submit() {
if (!this.hasUpdatedKey) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("updateKey")
);
return;
}
async submit() {
if (!this.hasUpdatedKey) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('updateKey'));
return;
}
const fileEl = document.getElementById('file') as HTMLInputElement;
const files = fileEl.files;
if (files == null || files.length === 0) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('selectFile'));
return;
}
if (files[0].size > 524288000) { // 500 MB
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('maxFileSize'));
return;
}
try {
this.formPromise = this.saveCipherAttachment(files[0]);
this.cipherDomain = await this.formPromise;
this.cipher = await this.cipherDomain.decrypt();
this.platformUtilsService.showToast('success', null, this.i18nService.t('attachmentSaved'));
this.onUploadedAttachment.emit();
} catch (e) {
this.logService.error(e);
}
// reset file input
// ref: https://stackoverflow.com/a/20552042
fileEl.type = '';
fileEl.type = 'file';
fileEl.value = '';
const fileEl = document.getElementById("file") as HTMLInputElement;
const files = fileEl.files;
if (files == null || files.length === 0) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("selectFile")
);
return;
}
async delete(attachment: AttachmentView) {
if (this.deletePromises[attachment.id] != null) {
return;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('deleteAttachmentConfirmation'), this.i18nService.t('deleteAttachment'),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return;
}
try {
this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id);
await this.deletePromises[attachment.id];
this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedAttachment'));
const i = this.cipher.attachments.indexOf(attachment);
if (i > -1) {
this.cipher.attachments.splice(i, 1);
}
} catch (e) {
this.logService.error(e);
}
this.deletePromises[attachment.id] = null;
this.onDeletedAttachment.emit();
if (files[0].size > 524288000) {
// 500 MB
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("maxFileSize")
);
return;
}
async download(attachment: AttachmentView) {
const a = (attachment as any);
if (a.downloading) {
return;
}
try {
this.formPromise = this.saveCipherAttachment(files[0]);
this.cipherDomain = await this.formPromise;
this.cipher = await this.cipherDomain.decrypt();
this.platformUtilsService.showToast("success", null, this.i18nService.t("attachmentSaved"));
this.onUploadedAttachment.emit();
} catch (e) {
this.logService.error(e);
}
if (!this.canAccessAttachments) {
this.platformUtilsService.showToast('error', this.i18nService.t('premiumRequired'),
this.i18nService.t('premiumRequiredDesc'));
return;
}
// reset file input
// ref: https://stackoverflow.com/a/20552042
fileEl.type = "";
fileEl.type = "file";
fileEl.value = "";
}
let url: string;
try {
const attachmentDownloadResponse = await this.apiService.getAttachmentData(this.cipher.id, attachment.id,
this.emergencyAccessId);
url = attachmentDownloadResponse.url;
} catch (e) {
if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) {
url = attachment.url;
} else if (e instanceof ErrorResponse) {
throw new Error((e as ErrorResponse).getSingleMessage());
} else {
throw e;
}
}
async delete(attachment: AttachmentView) {
if (this.deletePromises[attachment.id] != null) {
return;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("deleteAttachmentConfirmation"),
this.i18nService.t("deleteAttachment"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return;
}
try {
this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id);
await this.deletePromises[attachment.id];
this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedAttachment"));
const i = this.cipher.attachments.indexOf(attachment);
if (i > -1) {
this.cipher.attachments.splice(i, 1);
}
} catch (e) {
this.logService.error(e);
}
this.deletePromises[attachment.id] = null;
this.onDeletedAttachment.emit();
}
async download(attachment: AttachmentView) {
const a = attachment as any;
if (a.downloading) {
return;
}
if (!this.canAccessAttachments) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("premiumRequired"),
this.i18nService.t("premiumRequiredDesc")
);
return;
}
let url: string;
try {
const attachmentDownloadResponse = await this.apiService.getAttachmentData(
this.cipher.id,
attachment.id,
this.emergencyAccessId
);
url = attachmentDownloadResponse.url;
} catch (e) {
if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) {
url = attachment.url;
} else if (e instanceof ErrorResponse) {
throw new Error((e as ErrorResponse).getSingleMessage());
} else {
throw e;
}
}
a.downloading = true;
const response = await fetch(new Request(url, { cache: "no-store" }));
if (response.status !== 200) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
a.downloading = false;
return;
}
try {
const buf = await response.arrayBuffer();
const key =
attachment.key != null
? attachment.key
: await this.cryptoService.getOrgKey(this.cipher.organizationId);
const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
this.platformUtilsService.saveFile(this.win, decBuf, null, attachment.fileName);
} catch (e) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
}
a.downloading = false;
}
protected async init() {
this.cipherDomain = await this.loadCipher();
this.cipher = await this.cipherDomain.decrypt();
this.hasUpdatedKey = await this.cryptoService.hasEncKey();
const canAccessPremium = await this.stateService.getCanAccessPremium();
this.canAccessAttachments = canAccessPremium || this.cipher.organizationId != null;
if (!this.canAccessAttachments) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("premiumRequiredDesc"),
this.i18nService.t("premiumRequired"),
this.i18nService.t("learnMore"),
this.i18nService.t("cancel")
);
if (confirmed) {
this.platformUtilsService.launchUri("https://vault.bitwarden.com/#/?premium=purchase");
}
} else if (!this.hasUpdatedKey) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("updateKey"),
this.i18nService.t("featureUnavailable"),
this.i18nService.t("learnMore"),
this.i18nService.t("cancel"),
"warning"
);
if (confirmed) {
this.platformUtilsService.launchUri(
"https://help.bitwarden.com/article/update-encryption-key/"
);
}
}
}
protected async reuploadCipherAttachment(attachment: AttachmentView, admin: boolean) {
const a = attachment as any;
if (attachment.key != null || a.downloading || this.reuploadPromises[attachment.id] != null) {
return;
}
try {
this.reuploadPromises[attachment.id] = Promise.resolve().then(async () => {
// 1. Download
a.downloading = true;
const response = await fetch(new Request(url, { cache: 'no-store' }));
const response = await fetch(new Request(attachment.url, { cache: "no-store" }));
if (response.status !== 200) {
this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred'));
a.downloading = false;
return;
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
a.downloading = false;
return;
}
try {
const buf = await response.arrayBuffer();
const key = attachment.key != null ? attachment.key :
await this.cryptoService.getOrgKey(this.cipher.organizationId);
const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
this.platformUtilsService.saveFile(this.win, decBuf, null, attachment.fileName);
// 2. Resave
const buf = await response.arrayBuffer();
const key =
attachment.key != null
? attachment.key
: await this.cryptoService.getOrgKey(this.cipher.organizationId);
const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
this.cipherDomain = await this.cipherService.saveAttachmentRawWithServer(
this.cipherDomain,
attachment.fileName,
decBuf,
admin
);
this.cipher = await this.cipherDomain.decrypt();
// 3. Delete old
this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id);
await this.deletePromises[attachment.id];
const foundAttachment = this.cipher.attachments.filter((a2) => a2.id === attachment.id);
if (foundAttachment.length > 0) {
const i = this.cipher.attachments.indexOf(foundAttachment[0]);
if (i > -1) {
this.cipher.attachments.splice(i, 1);
}
}
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("attachmentSaved")
);
this.onReuploadedAttachment.emit();
} catch (e) {
this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred'));
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
}
a.downloading = false;
});
await this.reuploadPromises[attachment.id];
} catch (e) {
this.logService.error(e);
}
}
protected async init() {
this.cipherDomain = await this.loadCipher();
this.cipher = await this.cipherDomain.decrypt();
protected loadCipher() {
return this.cipherService.get(this.cipherId);
}
this.hasUpdatedKey = await this.cryptoService.hasEncKey();
const canAccessPremium = await this.stateService.getCanAccessPremium();
this.canAccessAttachments = canAccessPremium || this.cipher.organizationId != null;
protected saveCipherAttachment(file: File) {
return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file);
}
if (!this.canAccessAttachments) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('premiumRequiredDesc'), this.i18nService.t('premiumRequired'),
this.i18nService.t('learnMore'), this.i18nService.t('cancel'));
if (confirmed) {
this.platformUtilsService.launchUri('https://vault.bitwarden.com/#/?premium=purchase');
}
} else if (!this.hasUpdatedKey) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('updateKey'), this.i18nService.t('featureUnavailable'),
this.i18nService.t('learnMore'), this.i18nService.t('cancel'), 'warning');
if (confirmed) {
this.platformUtilsService.launchUri('https://help.bitwarden.com/article/update-encryption-key/');
}
}
}
protected async reuploadCipherAttachment(attachment: AttachmentView, admin: boolean) {
const a = (attachment as any);
if (attachment.key != null || a.downloading || this.reuploadPromises[attachment.id] != null) {
return;
}
try {
this.reuploadPromises[attachment.id] = Promise.resolve().then(async () => {
// 1. Download
a.downloading = true;
const response = await fetch(new Request(attachment.url, { cache: 'no-store' }));
if (response.status !== 200) {
this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred'));
a.downloading = false;
return;
}
try {
// 2. Resave
const buf = await response.arrayBuffer();
const key = attachment.key != null ? attachment.key :
await this.cryptoService.getOrgKey(this.cipher.organizationId);
const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
this.cipherDomain = await this.cipherService.saveAttachmentRawWithServer(
this.cipherDomain, attachment.fileName, decBuf, admin);
this.cipher = await this.cipherDomain.decrypt();
// 3. Delete old
this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id);
await this.deletePromises[attachment.id];
const foundAttachment = this.cipher.attachments.filter(a2 => a2.id === attachment.id);
if (foundAttachment.length > 0) {
const i = this.cipher.attachments.indexOf(foundAttachment[0]);
if (i > -1) {
this.cipher.attachments.splice(i, 1);
}
}
this.platformUtilsService.showToast('success', null, this.i18nService.t('attachmentSaved'));
this.onReuploadedAttachment.emit();
} catch (e) {
this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred'));
}
a.downloading = false;
});
await this.reuploadPromises[attachment.id];
} catch (e) {
this.logService.error(e);
}
}
protected loadCipher() {
return this.cipherService.get(this.cipherId);
}
protected saveCipherAttachment(file: File) {
return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file);
}
protected deleteCipherAttachment(attachmentId: string) {
return this.cipherService.deleteAttachmentWithServer(this.cipher.id, attachmentId);
}
protected deleteCipherAttachment(attachmentId: string) {
return this.cipherService.deleteAttachmentWithServer(this.cipher.id, attachmentId);
}
}

View File

@@ -1,138 +1,143 @@
import {
Component,
Input,
OnChanges,
OnInit,
} from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { Component, Input, OnChanges, OnInit } from "@angular/core";
import { DomSanitizer } from "@angular/platform-browser";
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
@Component({
selector: 'app-avatar',
template: '<img [src]="sanitizer.bypassSecurityTrustResourceUrl(src)" title="{{data}}" ' +
'[ngClass]="{\'rounded-circle\': circle}">',
selector: "app-avatar",
template:
'<img [src]="sanitizer.bypassSecurityTrustResourceUrl(src)" title="{{data}}" ' +
"[ngClass]=\"{'rounded-circle': circle}\">",
})
export class AvatarComponent implements OnChanges, OnInit {
@Input() data: string;
@Input() email: string;
@Input() size = 45;
@Input() charCount = 2;
@Input() textColor = '#ffffff';
@Input() fontSize = 20;
@Input() fontWeight = 300;
@Input() dynamic = false;
@Input() circle = false;
@Input() data: string;
@Input() email: string;
@Input() size = 45;
@Input() charCount = 2;
@Input() textColor = "#ffffff";
@Input() fontSize = 20;
@Input() fontWeight = 300;
@Input() dynamic = false;
@Input() circle = false;
src: string;
src: string;
constructor(public sanitizer: DomSanitizer, private cryptoFunctionService: CryptoFunctionService,
private stateService: StateService) { }
constructor(
public sanitizer: DomSanitizer,
private cryptoFunctionService: CryptoFunctionService,
private stateService: StateService
) {}
ngOnInit() {
if (!this.dynamic) {
this.generate();
}
ngOnInit() {
if (!this.dynamic) {
this.generate();
}
}
ngOnChanges() {
if (this.dynamic) {
this.generate();
}
ngOnChanges() {
if (this.dynamic) {
this.generate();
}
}
private async generate() {
const enableGravatars = await this.stateService.getEnableGravitars();
if (enableGravatars && this.email != null) {
const hashBytes = await this.cryptoFunctionService.hash(this.email.toLowerCase().trim(), 'md5');
const hash = Utils.fromBufferToHex(hashBytes).toLowerCase();
this.src = 'https://www.gravatar.com/avatar/' + hash + '?s=' + this.size + '&r=pg&d=retro';
} else {
let chars: string = null;
const upperData = this.data.toUpperCase();
private async generate() {
const enableGravatars = await this.stateService.getEnableGravitars();
if (enableGravatars && this.email != null) {
const hashBytes = await this.cryptoFunctionService.hash(
this.email.toLowerCase().trim(),
"md5"
);
const hash = Utils.fromBufferToHex(hashBytes).toLowerCase();
this.src = "https://www.gravatar.com/avatar/" + hash + "?s=" + this.size + "&r=pg&d=retro";
} else {
let chars: string = null;
const upperData = this.data.toUpperCase();
if (this.charCount > 1) {
chars = this.getFirstLetters(upperData, this.charCount);
}
if (chars == null) {
chars = this.unicodeSafeSubstring(upperData, this.charCount);
}
if (this.charCount > 1) {
chars = this.getFirstLetters(upperData, this.charCount);
}
if (chars == null) {
chars = this.unicodeSafeSubstring(upperData, this.charCount);
}
// If the chars contain an emoji, only show it.
if (chars.match(Utils.regexpEmojiPresentation)) {
chars = chars.match(Utils.regexpEmojiPresentation)[0];
}
// If the chars contain an emoji, only show it.
if (chars.match(Utils.regexpEmojiPresentation)) {
chars = chars.match(Utils.regexpEmojiPresentation)[0];
}
const charObj = this.getCharText(chars);
const color = this.stringToColor(upperData);
const svg = this.getSvg(this.size, color);
svg.appendChild(charObj);
const html = window.document.createElement('div').appendChild(svg).outerHTML;
const svgHtml = window.btoa(unescape(encodeURIComponent(html)));
this.src = 'data:image/svg+xml;base64,' + svgHtml;
}
const charObj = this.getCharText(chars);
const color = this.stringToColor(upperData);
const svg = this.getSvg(this.size, color);
svg.appendChild(charObj);
const html = window.document.createElement("div").appendChild(svg).outerHTML;
const svgHtml = window.btoa(unescape(encodeURIComponent(html)));
this.src = "data:image/svg+xml;base64," + svgHtml;
}
}
private stringToColor(str: string): string {
let hash = 0;
for (let i = 0; i < str.length; i++) {
// tslint:disable-next-line
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
let color = '#';
for (let i = 0; i < 3; i++) {
// tslint:disable-next-line
const value = (hash >> (i * 8)) & 0xFF;
color += ('00' + value.toString(16)).substr(-2);
}
return color;
private stringToColor(str: string): string {
let hash = 0;
for (let i = 0; i < str.length; i++) {
// tslint:disable-next-line
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
let color = "#";
for (let i = 0; i < 3; i++) {
// tslint:disable-next-line
const value = (hash >> (i * 8)) & 0xff;
color += ("00" + value.toString(16)).substr(-2);
}
return color;
}
private getFirstLetters(data: string, count: number): string {
const parts = data.split(' ');
if (parts.length > 1) {
let text = '';
for (let i = 0; i < count; i++) {
text += this.unicodeSafeSubstring(parts[i], 1);
}
return text;
}
return null;
private getFirstLetters(data: string, count: number): string {
const parts = data.split(" ");
if (parts.length > 1) {
let text = "";
for (let i = 0; i < count; i++) {
text += this.unicodeSafeSubstring(parts[i], 1);
}
return text;
}
return null;
}
private getSvg(size: number, color: string): HTMLElement {
const svgTag = window.document.createElement('svg');
svgTag.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svgTag.setAttribute('pointer-events', 'none');
svgTag.setAttribute('width', size.toString());
svgTag.setAttribute('height', size.toString());
svgTag.style.backgroundColor = color;
svgTag.style.width = size + 'px';
svgTag.style.height = size + 'px';
return svgTag;
}
private getSvg(size: number, color: string): HTMLElement {
const svgTag = window.document.createElement("svg");
svgTag.setAttribute("xmlns", "http://www.w3.org/2000/svg");
svgTag.setAttribute("pointer-events", "none");
svgTag.setAttribute("width", size.toString());
svgTag.setAttribute("height", size.toString());
svgTag.style.backgroundColor = color;
svgTag.style.width = size + "px";
svgTag.style.height = size + "px";
return svgTag;
}
private getCharText(character: string): HTMLElement {
const textTag = window.document.createElement('text');
textTag.setAttribute('text-anchor', 'middle');
textTag.setAttribute('y', '50%');
textTag.setAttribute('x', '50%');
textTag.setAttribute('dy', '0.35em');
textTag.setAttribute('pointer-events', 'auto');
textTag.setAttribute('fill', this.textColor);
textTag.setAttribute('font-family', '"Open Sans","Helvetica Neue",Helvetica,Arial,' +
'sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"');
textTag.textContent = character;
textTag.style.fontWeight = this.fontWeight.toString();
textTag.style.fontSize = this.fontSize + 'px';
return textTag;
}
private getCharText(character: string): HTMLElement {
const textTag = window.document.createElement("text");
textTag.setAttribute("text-anchor", "middle");
textTag.setAttribute("y", "50%");
textTag.setAttribute("x", "50%");
textTag.setAttribute("dy", "0.35em");
textTag.setAttribute("pointer-events", "auto");
textTag.setAttribute("fill", this.textColor);
textTag.setAttribute(
"font-family",
'"Open Sans","Helvetica Neue",Helvetica,Arial,' +
'sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"'
);
textTag.textContent = character;
textTag.style.fontWeight = this.fontWeight.toString();
textTag.style.fontSize = this.fontSize + "px";
return textTag;
}
private unicodeSafeSubstring(str: string, count: number) {
const characters = str.match(/./ug);
return characters != null ? characters.slice(0, count).join('') : '';
}
private unicodeSafeSubstring(str: string, count: number) {
const characters = str.match(/./gu);
return characters != null ? characters.slice(0, count).join("") : "";
}
}

View File

@@ -1,27 +1,35 @@
<div #callout class="callout callout-{{calloutStyle}}" [ngClass]="{'clickable': clickable}"
[attr.role]="useAlertRole ? 'alert' : null">
<h3 class="callout-heading" *ngIf="title">
<i class="fa {{icon}}" *ngIf="icon" aria-hidden="true"></i>
{{title}}
</h3>
<div class="enforced-policy-options" *ngIf="enforcedPolicyOptions">
{{enforcedPolicyMessage}}
<ul>
<li *ngIf="enforcedPolicyOptions?.minComplexity > 0">
{{'policyInEffectMinComplexity' | i18n : getPasswordScoreAlertDisplay()}}
</li>
<li *ngIf="enforcedPolicyOptions?.minLength > 0">
{{'policyInEffectMinLength' | i18n : enforcedPolicyOptions?.minLength.toString()}}
</li>
<li *ngIf="enforcedPolicyOptions?.requireUpper">
{{'policyInEffectUppercase' | i18n}}</li>
<li *ngIf="enforcedPolicyOptions?.requireLower">
{{'policyInEffectLowercase' | i18n}}</li>
<li *ngIf="enforcedPolicyOptions?.requireNumbers">
{{'policyInEffectNumbers' | i18n}}</li>
<li *ngIf="enforcedPolicyOptions?.requireSpecial">
{{'policyInEffectSpecial' | i18n : '!@#$%^&*'}}</li>
</ul>
</div>
<ng-content></ng-content>
<div
#callout
class="callout callout-{{ calloutStyle }}"
[ngClass]="{ clickable: clickable }"
[attr.role]="useAlertRole ? 'alert' : null"
>
<h3 class="callout-heading" *ngIf="title">
<i class="fa {{ icon }}" *ngIf="icon" aria-hidden="true"></i>
{{ title }}
</h3>
<div class="enforced-policy-options" *ngIf="enforcedPolicyOptions">
{{ enforcedPolicyMessage }}
<ul>
<li *ngIf="enforcedPolicyOptions?.minComplexity > 0">
{{ "policyInEffectMinComplexity" | i18n: getPasswordScoreAlertDisplay() }}
</li>
<li *ngIf="enforcedPolicyOptions?.minLength > 0">
{{ "policyInEffectMinLength" | i18n: enforcedPolicyOptions?.minLength.toString() }}
</li>
<li *ngIf="enforcedPolicyOptions?.requireUpper">
{{ "policyInEffectUppercase" | i18n }}
</li>
<li *ngIf="enforcedPolicyOptions?.requireLower">
{{ "policyInEffectLowercase" | i18n }}
</li>
<li *ngIf="enforcedPolicyOptions?.requireNumbers">
{{ "policyInEffectNumbers" | i18n }}
</li>
<li *ngIf="enforcedPolicyOptions?.requireSpecial">
{{ "policyInEffectSpecial" | i18n: "!@#$%^&*" }}
</li>
</ul>
</div>
<ng-content></ng-content>
</div>

View File

@@ -1,83 +1,79 @@
import {
Component,
Input,
OnInit,
} from '@angular/core';
import { Component, Input, OnInit } from "@angular/core";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MasterPasswordPolicyOptions } from 'jslib-common/models/domain/masterPasswordPolicyOptions';
import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPasswordPolicyOptions";
@Component({
selector: 'app-callout',
templateUrl: 'callout.component.html',
selector: "app-callout",
templateUrl: "callout.component.html",
})
export class CalloutComponent implements OnInit {
@Input() type = 'info';
@Input() icon: string;
@Input() title: string;
@Input() clickable: boolean;
@Input() enforcedPolicyOptions: MasterPasswordPolicyOptions;
@Input() enforcedPolicyMessage: string;
@Input() useAlertRole = false;
@Input() type = "info";
@Input() icon: string;
@Input() title: string;
@Input() clickable: boolean;
@Input() enforcedPolicyOptions: MasterPasswordPolicyOptions;
@Input() enforcedPolicyMessage: string;
@Input() useAlertRole = false;
calloutStyle: string;
calloutStyle: string;
constructor(private i18nService: I18nService) { }
constructor(private i18nService: I18nService) {}
ngOnInit() {
this.calloutStyle = this.type;
ngOnInit() {
this.calloutStyle = this.type;
if (this.enforcedPolicyMessage === undefined) {
this.enforcedPolicyMessage = this.i18nService.t('masterPasswordPolicyInEffect');
}
if (this.type === 'warning' || this.type === 'danger') {
if (this.type === 'danger') {
this.calloutStyle = 'danger';
}
if (this.title === undefined) {
this.title = this.i18nService.t('warning');
}
if (this.icon === undefined) {
this.icon = 'fa-warning';
}
} else if (this.type === 'error') {
this.calloutStyle = 'danger';
if (this.title === undefined) {
this.title = this.i18nService.t('error');
}
if (this.icon === undefined) {
this.icon = 'fa-bolt';
}
} else if (this.type === 'tip') {
this.calloutStyle = 'success';
if (this.title === undefined) {
this.title = this.i18nService.t('tip');
}
if (this.icon === undefined) {
this.icon = 'fa-lightbulb-o';
}
}
if (this.enforcedPolicyMessage === undefined) {
this.enforcedPolicyMessage = this.i18nService.t("masterPasswordPolicyInEffect");
}
getPasswordScoreAlertDisplay() {
if (this.enforcedPolicyOptions == null) {
return '';
}
let str: string;
switch (this.enforcedPolicyOptions.minComplexity) {
case 4:
str = this.i18nService.t('strong');
break;
case 3:
str = this.i18nService.t('good');
break;
default:
str = this.i18nService.t('weak');
break;
}
return str + ' (' + this.enforcedPolicyOptions.minComplexity + ')';
if (this.type === "warning" || this.type === "danger") {
if (this.type === "danger") {
this.calloutStyle = "danger";
}
if (this.title === undefined) {
this.title = this.i18nService.t("warning");
}
if (this.icon === undefined) {
this.icon = "fa-warning";
}
} else if (this.type === "error") {
this.calloutStyle = "danger";
if (this.title === undefined) {
this.title = this.i18nService.t("error");
}
if (this.icon === undefined) {
this.icon = "fa-bolt";
}
} else if (this.type === "tip") {
this.calloutStyle = "success";
if (this.title === undefined) {
this.title = this.i18nService.t("tip");
}
if (this.icon === undefined) {
this.icon = "fa-lightbulb-o";
}
}
}
getPasswordScoreAlertDisplay() {
if (this.enforcedPolicyOptions == null) {
return "";
}
let str: string;
switch (this.enforcedPolicyOptions.minComplexity) {
case 4:
str = this.i18nService.t("strong");
break;
case 3:
str = this.i18nService.t("good");
break;
default:
str = this.i18nService.t("weak");
break;
}
return str + " (" + this.enforcedPolicyOptions.minComplexity + ")";
}
}

View File

@@ -1,47 +1,55 @@
import { Directive, Input } from '@angular/core';
import { Directive, Input } from "@angular/core";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { CaptchaIFrame } from 'jslib-common/misc/captcha_iframe';
import { CaptchaIFrame } from "jslib-common/misc/captcha_iframe";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
@Directive()
export abstract class CaptchaProtectedComponent {
@Input() captchaSiteKey: string = null;
captchaToken: string = null;
captcha: CaptchaIFrame;
@Input() captchaSiteKey: string = null;
captchaToken: string = null;
captcha: CaptchaIFrame;
constructor(protected environmentService: EnvironmentService, protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService) { }
constructor(
protected environmentService: EnvironmentService,
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService
) {}
async setupCaptcha() {
const webVaultUrl = this.environmentService.getWebVaultUrl();
async setupCaptcha() {
const webVaultUrl = this.environmentService.getWebVaultUrl();
this.captcha = new CaptchaIFrame(window, webVaultUrl,
this.i18nService, (token: string) => {
this.captchaToken = token;
}, (error: string) => {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), error);
}, (info: string) => {
this.platformUtilsService.showToast('info', this.i18nService.t('info'), info);
}
);
this.captcha = new CaptchaIFrame(
window,
webVaultUrl,
this.i18nService,
(token: string) => {
this.captchaToken = token;
},
(error: string) => {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), error);
},
(info: string) => {
this.platformUtilsService.showToast("info", this.i18nService.t("info"), info);
}
);
}
showCaptcha() {
return !Utils.isNullOrWhitespace(this.captchaSiteKey);
}
protected handleCaptchaRequired(response: { captchaSiteKey: string }): boolean {
if (Utils.isNullOrWhitespace(response.captchaSiteKey)) {
return false;
}
showCaptcha() {
return !Utils.isNullOrWhitespace(this.captchaSiteKey);
}
protected handleCaptchaRequired(response: { captchaSiteKey: string; }): boolean {
if (Utils.isNullOrWhitespace(response.captchaSiteKey)) {
return false;
}
this.captchaSiteKey = response.captchaSiteKey;
this.captcha.init(response.captchaSiteKey);
return true;
}
this.captchaSiteKey = response.captchaSiteKey;
this.captcha.init(response.captchaSiteKey);
return true;
}
}

View File

@@ -1,152 +1,197 @@
import { Directive, OnInit } from '@angular/core';
import { Directive, OnInit } from "@angular/core";
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { EncString } from 'jslib-common/models/domain/encString';
import { MasterPasswordPolicyOptions } from 'jslib-common/models/domain/masterPasswordPolicyOptions';
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
import { EncString } from "jslib-common/models/domain/encString";
import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPasswordPolicyOptions";
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
import { KdfType } from 'jslib-common/enums/kdfType';
import { KdfType } from "jslib-common/enums/kdfType";
@Directive()
export class ChangePasswordComponent implements OnInit {
masterPassword: string;
masterPasswordRetype: string;
formPromise: Promise<any>;
masterPasswordScore: number;
enforcedPolicyOptions: MasterPasswordPolicyOptions;
masterPassword: string;
masterPasswordRetype: string;
formPromise: Promise<any>;
masterPasswordScore: number;
enforcedPolicyOptions: MasterPasswordPolicyOptions;
protected email: string;
protected kdf: KdfType;
protected kdfIterations: number;
protected email: string;
protected kdf: KdfType;
protected kdfIterations: number;
private masterPasswordStrengthTimeout: any;
private masterPasswordStrengthTimeout: any;
constructor(protected i18nService: I18nService, protected cryptoService: CryptoService,
protected messagingService: MessagingService, protected passwordGenerationService: PasswordGenerationService,
protected platformUtilsService: PlatformUtilsService, protected policyService: PolicyService,
protected stateService: StateService) { }
constructor(
protected i18nService: I18nService,
protected cryptoService: CryptoService,
protected messagingService: MessagingService,
protected passwordGenerationService: PasswordGenerationService,
protected platformUtilsService: PlatformUtilsService,
protected policyService: PolicyService,
protected stateService: StateService
) {}
async ngOnInit() {
this.email = await this.stateService.getEmail();
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
async ngOnInit() {
this.email = await this.stateService.getEmail();
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
}
async submit() {
if (!(await this.strongPassword())) {
return;
}
async submit() {
if (!await this.strongPassword()) {
return;
}
if (!await this.setupSubmitActions()) {
return;
}
const email = await this.stateService.getEmail();
if (this.kdf == null) {
this.kdf = await this.stateService.getKdfType();
}
if (this.kdfIterations == null) {
this.kdfIterations = await this.stateService.getKdfIterations();
}
const key = await this.cryptoService.makeKey(this.masterPassword, email.trim().toLowerCase(),
this.kdf, this.kdfIterations);
const masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, key);
let encKey: [SymmetricCryptoKey, EncString] = null;
const existingEncKey = await this.cryptoService.getEncKey();
if (existingEncKey == null) {
encKey = await this.cryptoService.makeEncKey(key);
} else {
encKey = await this.cryptoService.remakeEncKey(key);
}
await this.performSubmitActions(masterPasswordHash, key, encKey);
if (!(await this.setupSubmitActions())) {
return;
}
async setupSubmitActions(): Promise<boolean> {
// Override in sub-class
// Can be used for additional validation and/or other processes the should occur before changing passwords
return true;
const email = await this.stateService.getEmail();
if (this.kdf == null) {
this.kdf = await this.stateService.getKdfType();
}
if (this.kdfIterations == null) {
this.kdfIterations = await this.stateService.getKdfIterations();
}
const key = await this.cryptoService.makeKey(
this.masterPassword,
email.trim().toLowerCase(),
this.kdf,
this.kdfIterations
);
const masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, key);
let encKey: [SymmetricCryptoKey, EncString] = null;
const existingEncKey = await this.cryptoService.getEncKey();
if (existingEncKey == null) {
encKey = await this.cryptoService.makeEncKey(key);
} else {
encKey = await this.cryptoService.remakeEncKey(key);
}
async performSubmitActions(masterPasswordHash: string, key: SymmetricCryptoKey,
encKey: [SymmetricCryptoKey, EncString]) {
// Override in sub-class
await this.performSubmitActions(masterPasswordHash, key, encKey);
}
async setupSubmitActions(): Promise<boolean> {
// Override in sub-class
// Can be used for additional validation and/or other processes the should occur before changing passwords
return true;
}
async performSubmitActions(
masterPasswordHash: string,
key: SymmetricCryptoKey,
encKey: [SymmetricCryptoKey, EncString]
) {
// Override in sub-class
}
async strongPassword(): Promise<boolean> {
if (this.masterPassword == null || this.masterPassword === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPassRequired")
);
return false;
}
if (this.masterPassword.length < 8) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPassLength")
);
return false;
}
if (this.masterPassword !== this.masterPasswordRetype) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPassDoesntMatch")
);
return false;
}
async strongPassword(): Promise<boolean> {
if (this.masterPassword == null || this.masterPassword === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassRequired'));
return false;
}
if (this.masterPassword.length < 8) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassLength'));
return false;
}
if (this.masterPassword !== this.masterPasswordRetype) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassDoesntMatch'));
return false;
}
const strengthResult = this.passwordGenerationService.passwordStrength(
this.masterPassword,
this.getPasswordStrengthUserInput()
);
const strengthResult = this.passwordGenerationService.passwordStrength(this.masterPassword,
this.getPasswordStrengthUserInput());
if (this.enforcedPolicyOptions != null &&
!this.policyService.evaluateMasterPassword(
strengthResult.score,
this.masterPassword,
this.enforcedPolicyOptions)) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPasswordPolicyRequirementsNotMet'));
return false;
}
if (strengthResult != null && strengthResult.score < 3) {
const result = await this.platformUtilsService.showDialog(this.i18nService.t('weakMasterPasswordDesc'),
this.i18nService.t('weakMasterPassword'), this.i18nService.t('yes'), this.i18nService.t('no'),
'warning');
if (!result) {
return false;
}
}
return true;
if (
this.enforcedPolicyOptions != null &&
!this.policyService.evaluateMasterPassword(
strengthResult.score,
this.masterPassword,
this.enforcedPolicyOptions
)
) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPasswordPolicyRequirementsNotMet")
);
return false;
}
updatePasswordStrength() {
if (this.masterPasswordStrengthTimeout != null) {
clearTimeout(this.masterPasswordStrengthTimeout);
}
this.masterPasswordStrengthTimeout = setTimeout(() => {
const strengthResult = this.passwordGenerationService.passwordStrength(this.masterPassword,
this.getPasswordStrengthUserInput());
this.masterPasswordScore = strengthResult == null ? null : strengthResult.score;
}, 300);
if (strengthResult != null && strengthResult.score < 3) {
const result = await this.platformUtilsService.showDialog(
this.i18nService.t("weakMasterPasswordDesc"),
this.i18nService.t("weakMasterPassword"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!result) {
return false;
}
}
async logOut() {
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('logOutConfirmation'),
this.i18nService.t('logOut'), this.i18nService.t('logOut'), this.i18nService.t('cancel'));
if (confirmed) {
this.messagingService.send('logout');
}
}
return true;
}
private getPasswordStrengthUserInput() {
let userInput: string[] = [];
const atPosition = this.email.indexOf('@');
if (atPosition > -1) {
userInput = userInput.concat(this.email.substr(0, atPosition).trim().toLowerCase().split(/[^A-Za-z0-9]/));
}
return userInput;
updatePasswordStrength() {
if (this.masterPasswordStrengthTimeout != null) {
clearTimeout(this.masterPasswordStrengthTimeout);
}
this.masterPasswordStrengthTimeout = setTimeout(() => {
const strengthResult = this.passwordGenerationService.passwordStrength(
this.masterPassword,
this.getPasswordStrengthUserInput()
);
this.masterPasswordScore = strengthResult == null ? null : strengthResult.score;
}, 300);
}
async logOut() {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("logOutConfirmation"),
this.i18nService.t("logOut"),
this.i18nService.t("logOut"),
this.i18nService.t("cancel")
);
if (confirmed) {
this.messagingService.send("logout");
}
}
private getPasswordStrengthUserInput() {
let userInput: string[] = [];
const atPosition = this.email.indexOf("@");
if (atPosition > -1) {
userInput = userInput.concat(
this.email
.substr(0, atPosition)
.trim()
.toLowerCase()
.split(/[^A-Za-z0-9]/)
);
}
return userInput;
}
}

View File

@@ -1,95 +1,94 @@
import {
Directive,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { Directive, EventEmitter, Input, Output } from "@angular/core";
import { SearchService } from 'jslib-common/abstractions/search.service';
import { SearchService } from "jslib-common/abstractions/search.service";
import { CipherView } from 'jslib-common/models/view/cipherView';
import { CipherView } from "jslib-common/models/view/cipherView";
@Directive()
export class CiphersComponent {
@Input() activeCipherId: string = null;
@Output() onCipherClicked = new EventEmitter<CipherView>();
@Output() onCipherRightClicked = new EventEmitter<CipherView>();
@Output() onAddCipher = new EventEmitter();
@Output() onAddCipherOptions = new EventEmitter();
@Input() activeCipherId: string = null;
@Output() onCipherClicked = new EventEmitter<CipherView>();
@Output() onCipherRightClicked = new EventEmitter<CipherView>();
@Output() onAddCipher = new EventEmitter();
@Output() onAddCipherOptions = new EventEmitter();
loaded: boolean = false;
ciphers: CipherView[] = [];
searchText: string;
searchPlaceholder: string = null;
filter: (cipher: CipherView) => boolean = null;
deleted: boolean = false;
loaded: boolean = false;
ciphers: CipherView[] = [];
searchText: string;
searchPlaceholder: string = null;
filter: (cipher: CipherView) => boolean = null;
deleted: boolean = false;
protected searchPending = false;
protected searchPending = false;
private searchTimeout: any = null;
private searchTimeout: any = null;
constructor(protected searchService: SearchService) { }
constructor(protected searchService: SearchService) {}
async load(filter: (cipher: CipherView) => boolean = null, deleted: boolean = false) {
this.deleted = deleted || false;
await this.applyFilter(filter);
this.loaded = true;
async load(filter: (cipher: CipherView) => boolean = null, deleted: boolean = false) {
this.deleted = deleted || false;
await this.applyFilter(filter);
this.loaded = true;
}
async reload(filter: (cipher: CipherView) => boolean = null, deleted: boolean = false) {
this.loaded = false;
this.ciphers = [];
await this.load(filter, deleted);
}
async refresh() {
await this.reload(this.filter, this.deleted);
}
async applyFilter(filter: (cipher: CipherView) => boolean = null) {
this.filter = filter;
await this.search(null);
}
async search(timeout: number = null, indexedCiphers?: CipherView[]) {
this.searchPending = false;
if (this.searchTimeout != null) {
clearTimeout(this.searchTimeout);
}
async reload(filter: (cipher: CipherView) => boolean = null, deleted: boolean = false) {
this.loaded = false;
this.ciphers = [];
await this.load(filter, deleted);
if (timeout == null) {
await this.doSearch(indexedCiphers);
return;
}
this.searchPending = true;
this.searchTimeout = setTimeout(async () => {
await this.doSearch(indexedCiphers);
this.searchPending = false;
}, timeout);
}
async refresh() {
await this.reload(this.filter, this.deleted);
}
selectCipher(cipher: CipherView) {
this.onCipherClicked.emit(cipher);
}
async applyFilter(filter: (cipher: CipherView) => boolean = null) {
this.filter = filter;
await this.search(null);
}
rightClickCipher(cipher: CipherView) {
this.onCipherRightClicked.emit(cipher);
}
async search(timeout: number = null, indexedCiphers?: CipherView[]) {
this.searchPending = false;
if (this.searchTimeout != null) {
clearTimeout(this.searchTimeout);
}
if (timeout == null) {
await this.doSearch(indexedCiphers);
return;
}
this.searchPending = true;
this.searchTimeout = setTimeout(async () => {
await this.doSearch(indexedCiphers);
this.searchPending = false;
}, timeout);
}
addCipher() {
this.onAddCipher.emit();
}
selectCipher(cipher: CipherView) {
this.onCipherClicked.emit(cipher);
}
addCipherOptions() {
this.onAddCipherOptions.emit();
}
rightClickCipher(cipher: CipherView) {
this.onCipherRightClicked.emit(cipher);
}
isSearching() {
return !this.searchPending && this.searchService.isSearchable(this.searchText);
}
addCipher() {
this.onAddCipher.emit();
}
protected deletedFilter: (cipher: CipherView) => boolean = (c) => c.isDeleted === this.deleted;
addCipherOptions() {
this.onAddCipherOptions.emit();
}
isSearching() {
return !this.searchPending && this.searchService.isSearchable(this.searchText);
}
protected deletedFilter: (cipher: CipherView) => boolean = c => c.isDeleted === this.deleted;
protected async doSearch(indexedCiphers?: CipherView[]) {
this.ciphers = await this.searchService.searchCiphers(this.searchText, [this.filter, this.deletedFilter], indexedCiphers);
}
protected async doSearch(indexedCiphers?: CipherView[]) {
this.ciphers = await this.searchService.searchCiphers(
this.searchText,
[this.filter, this.deletedFilter],
indexedCiphers
);
}
}

View File

@@ -1,90 +1,94 @@
import {
Directive,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { CipherView } from 'jslib-common/models/view/cipherView';
import { CollectionView } from 'jslib-common/models/view/collectionView';
import { CipherView } from "jslib-common/models/view/cipherView";
import { CollectionView } from "jslib-common/models/view/collectionView";
import { Cipher } from 'jslib-common/models/domain/cipher';
import { Cipher } from "jslib-common/models/domain/cipher";
@Directive()
export class CollectionsComponent implements OnInit {
@Input() cipherId: string;
@Input() allowSelectNone = false;
@Output() onSavedCollections = new EventEmitter();
@Input() cipherId: string;
@Input() allowSelectNone = false;
@Output() onSavedCollections = new EventEmitter();
formPromise: Promise<any>;
cipher: CipherView;
collectionIds: string[];
collections: CollectionView[] = [];
formPromise: Promise<any>;
cipher: CipherView;
collectionIds: string[];
collections: CollectionView[] = [];
protected cipherDomain: Cipher;
protected cipherDomain: Cipher;
constructor(protected collectionService: CollectionService, protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService, protected cipherService: CipherService, private logService: LogService) { }
constructor(
protected collectionService: CollectionService,
protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService,
protected cipherService: CipherService,
private logService: LogService
) {}
async ngOnInit() {
await this.load();
async ngOnInit() {
await this.load();
}
async load() {
this.cipherDomain = await this.loadCipher();
this.collectionIds = this.loadCipherCollections();
this.cipher = await this.cipherDomain.decrypt();
this.collections = await this.loadCollections();
this.collections.forEach((c) => ((c as any).checked = false));
if (this.collectionIds != null) {
this.collections.forEach((c) => {
(c as any).checked = this.collectionIds != null && this.collectionIds.indexOf(c.id) > -1;
});
}
}
async load() {
this.cipherDomain = await this.loadCipher();
this.collectionIds = this.loadCipherCollections();
this.cipher = await this.cipherDomain.decrypt();
this.collections = await this.loadCollections();
this.collections.forEach(c => (c as any).checked = false);
if (this.collectionIds != null) {
this.collections.forEach(c => {
(c as any).checked = this.collectionIds != null && this.collectionIds.indexOf(c.id) > -1;
});
}
async submit() {
const selectedCollectionIds = this.collections
.filter((c) => !!(c as any).checked)
.map((c) => c.id);
if (!this.allowSelectNone && selectedCollectionIds.length === 0) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("selectOneCollection")
);
return;
}
async submit() {
const selectedCollectionIds = this.collections
.filter(c => !!(c as any).checked)
.map(c => c.id);
if (!this.allowSelectNone && selectedCollectionIds.length === 0) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('selectOneCollection'));
return;
}
this.cipherDomain.collectionIds = selectedCollectionIds;
try {
this.formPromise = this.saveCollections();
await this.formPromise;
this.onSavedCollections.emit();
this.platformUtilsService.showToast('success', null, this.i18nService.t('editedItem'));
} catch (e) {
this.logService.error(e);
}
this.cipherDomain.collectionIds = selectedCollectionIds;
try {
this.formPromise = this.saveCollections();
await this.formPromise;
this.onSavedCollections.emit();
this.platformUtilsService.showToast("success", null, this.i18nService.t("editedItem"));
} catch (e) {
this.logService.error(e);
}
}
protected loadCipher() {
return this.cipherService.get(this.cipherId);
}
protected loadCipher() {
return this.cipherService.get(this.cipherId);
}
protected loadCipherCollections() {
return this.cipherDomain.collectionIds;
}
protected loadCipherCollections() {
return this.cipherDomain.collectionIds;
}
protected async loadCollections() {
const allCollections = await this.collectionService.getAllDecrypted();
return allCollections.filter(c => !c.readOnly && c.organizationId === this.cipher.organizationId);
}
protected async loadCollections() {
const allCollections = await this.collectionService.getAllDecrypted();
return allCollections.filter(
(c) => !c.readOnly && c.organizationId === this.cipher.organizationId
);
}
protected saveCollections() {
return this.cipherService.saveCollectionsWithServer(this.cipherDomain);
}
protected saveCollections() {
return this.cipherService.saveCollectionsWithServer(this.cipherDomain);
}
}

View File

@@ -1,65 +1,63 @@
import {
Directive,
EventEmitter,
Output,
} from '@angular/core';
import { Directive, EventEmitter, Output } from "@angular/core";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
@Directive()
export class EnvironmentComponent {
@Output() onSaved = new EventEmitter();
@Output() onSaved = new EventEmitter();
iconsUrl: string;
identityUrl: string;
apiUrl: string;
webVaultUrl: string;
notificationsUrl: string;
baseUrl: string;
showCustom = false;
iconsUrl: string;
identityUrl: string;
apiUrl: string;
webVaultUrl: string;
notificationsUrl: string;
baseUrl: string;
showCustom = false;
constructor(protected platformUtilsService: PlatformUtilsService, protected environmentService: EnvironmentService,
protected i18nService: I18nService) {
constructor(
protected platformUtilsService: PlatformUtilsService,
protected environmentService: EnvironmentService,
protected i18nService: I18nService
) {
const urls = this.environmentService.getUrls();
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 || "";
}
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,
});
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;
// 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();
}
this.platformUtilsService.showToast('success', null, this.i18nService.t('environmentSaved'));
this.saved();
}
toggleCustom() {
this.showCustom = !this.showCustom;
}
toggleCustom() {
this.showCustom = !this.showCustom;
}
protected saved() {
this.onSaved.emit();
}
protected saved() {
this.onSaved.emit();
}
}

View File

@@ -1,140 +1,156 @@
import {
Directive,
EventEmitter,
OnInit,
Output,
} from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { Directive, EventEmitter, OnInit, Output } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EventService } from 'jslib-common/abstractions/event.service';
import { ExportService } from 'jslib-common/abstractions/export.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { UserVerificationService } from 'jslib-common/abstractions/userVerification.service';
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EventService } from "jslib-common/abstractions/event.service";
import { ExportService } from "jslib-common/abstractions/export.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { UserVerificationService } from "jslib-common/abstractions/userVerification.service";
import { EventType } from 'jslib-common/enums/eventType';
import { PolicyType } from 'jslib-common/enums/policyType';
import { EventType } from "jslib-common/enums/eventType";
import { PolicyType } from "jslib-common/enums/policyType";
@Directive()
export class ExportComponent implements OnInit {
@Output() onSaved = new EventEmitter();
@Output() onSaved = new EventEmitter();
formPromise: Promise<string>;
disabledByPolicy: boolean = false;
formPromise: Promise<string>;
disabledByPolicy: boolean = false;
exportForm = this.fb.group({
format: ['json'],
secret: [''],
});
exportForm = this.fb.group({
format: ["json"],
secret: [""],
});
formatOptions = [
{ name: '.json', value: 'json' },
{ name: '.csv', value: 'csv' },
{ name: '.json (Encrypted)', value: 'encrypted_json' },
];
formatOptions = [
{ name: ".json", value: "json" },
{ name: ".csv", value: "csv" },
{ name: ".json (Encrypted)", value: "encrypted_json" },
];
constructor(protected cryptoService: CryptoService, protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService, protected exportService: ExportService,
protected eventService: EventService, private policyService: PolicyService, protected win: Window,
private logService: LogService, private userVerificationService: UserVerificationService,
private fb: FormBuilder) { }
constructor(
protected cryptoService: CryptoService,
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected exportService: ExportService,
protected eventService: EventService,
private policyService: PolicyService,
protected win: Window,
private logService: LogService,
private userVerificationService: UserVerificationService,
private fb: FormBuilder
) {}
async ngOnInit() {
await this.checkExportDisabled();
async ngOnInit() {
await this.checkExportDisabled();
}
async checkExportDisabled() {
this.disabledByPolicy = await this.policyService.policyAppliesToUser(
PolicyType.DisablePersonalVaultExport
);
if (this.disabledByPolicy) {
this.exportForm.disable();
}
}
get encryptedFormat() {
return this.format === "encrypted_json";
}
async submit() {
if (this.disabledByPolicy) {
this.platformUtilsService.showToast(
"error",
null,
this.i18nService.t("personalVaultExportPolicyInEffect")
);
return;
}
async checkExportDisabled() {
this.disabledByPolicy = await this.policyService.policyAppliesToUser(PolicyType.DisablePersonalVaultExport);
if (this.disabledByPolicy) {
this.exportForm.disable();
}
const acceptedWarning = await this.warningDialog();
if (!acceptedWarning) {
return;
}
get encryptedFormat() {
return this.format === 'encrypted_json';
const secret = this.exportForm.get("secret").value;
try {
await this.userVerificationService.verifyUser(secret);
} catch (e) {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message);
return;
}
async submit() {
if (this.disabledByPolicy) {
this.platformUtilsService.showToast('error', null, this.i18nService.t('personalVaultExportPolicyInEffect'));
return;
}
const acceptedWarning = await this.warningDialog();
if (!acceptedWarning) {
return;
}
const secret = this.exportForm.get('secret').value;
try {
await this.userVerificationService.verifyUser(secret);
} catch (e) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), e.message);
return;
}
try {
this.formPromise = this.getExportData();
const data = await this.formPromise;
this.downloadFile(data);
this.saved();
await this.collectEvent();
this.exportForm.get('secret').setValue('');
} catch (e) {
this.logService.error(e);
}
try {
this.formPromise = this.getExportData();
const data = await this.formPromise;
this.downloadFile(data);
this.saved();
await this.collectEvent();
this.exportForm.get("secret").setValue("");
} catch (e) {
this.logService.error(e);
}
}
async warningDialog() {
if (this.encryptedFormat) {
return await this.platformUtilsService.showDialog(
'<p>' + this.i18nService.t('encExportKeyWarningDesc') +
'<p>' + this.i18nService.t('encExportAccountWarningDesc'),
this.i18nService.t('confirmVaultExport'), this.i18nService.t('exportVault'),
this.i18nService.t('cancel'), 'warning',
true);
} else {
return await this.platformUtilsService.showDialog(
this.i18nService.t('exportWarningDesc'),
this.i18nService.t('confirmVaultExport'), this.i18nService.t('exportVault'),
this.i18nService.t('cancel'), 'warning');
}
async warningDialog() {
if (this.encryptedFormat) {
return await this.platformUtilsService.showDialog(
"<p>" +
this.i18nService.t("encExportKeyWarningDesc") +
"<p>" +
this.i18nService.t("encExportAccountWarningDesc"),
this.i18nService.t("confirmVaultExport"),
this.i18nService.t("exportVault"),
this.i18nService.t("cancel"),
"warning",
true
);
} else {
return await this.platformUtilsService.showDialog(
this.i18nService.t("exportWarningDesc"),
this.i18nService.t("confirmVaultExport"),
this.i18nService.t("exportVault"),
this.i18nService.t("cancel"),
"warning"
);
}
}
protected saved() {
this.onSaved.emit();
}
protected saved() {
this.onSaved.emit();
}
protected getExportData() {
return this.exportService.getExport(this.format);
}
protected getExportData() {
return this.exportService.getExport(this.format);
}
protected getFileName(prefix?: string) {
let extension = this.format;
if (this.format === 'encrypted_json') {
if (prefix == null) {
prefix = 'encrypted';
} else {
prefix = 'encrypted_' + prefix;
}
extension = 'json';
}
return this.exportService.getFileName(prefix, extension);
protected getFileName(prefix?: string) {
let extension = this.format;
if (this.format === "encrypted_json") {
if (prefix == null) {
prefix = "encrypted";
} else {
prefix = "encrypted_" + prefix;
}
extension = "json";
}
return this.exportService.getFileName(prefix, extension);
}
protected async collectEvent(): Promise<any> {
await this.eventService.collect(EventType.User_ClientExportedVault);
}
protected async collectEvent(): Promise<any> {
await this.eventService.collect(EventType.User_ClientExportedVault);
}
get format() {
return this.exportForm.get('format').value;
}
get format() {
return this.exportForm.get("format").value;
}
private downloadFile(csv: string): void {
const fileName = this.getFileName();
this.platformUtilsService.saveFile(this.win, csv, { type: 'text/plain' }, fileName);
}
private downloadFile(csv: string): void {
const fileName = this.getFileName();
this.platformUtilsService.saveFile(this.win, csv, { type: "text/plain" }, fileName);
}
}

View File

@@ -1,89 +1,97 @@
import {
Directive,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { FolderService } from 'jslib-common/abstractions/folder.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { FolderService } from "jslib-common/abstractions/folder.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { FolderView } from 'jslib-common/models/view/folderView';
import { FolderView } from "jslib-common/models/view/folderView";
@Directive()
export class FolderAddEditComponent implements OnInit {
@Input() folderId: string;
@Output() onSavedFolder = new EventEmitter<FolderView>();
@Output() onDeletedFolder = new EventEmitter<FolderView>();
@Input() folderId: string;
@Output() onSavedFolder = new EventEmitter<FolderView>();
@Output() onDeletedFolder = new EventEmitter<FolderView>();
editMode: boolean = false;
folder: FolderView = new FolderView();
title: string;
formPromise: Promise<any>;
deletePromise: Promise<any>;
editMode: boolean = false;
folder: FolderView = new FolderView();
title: string;
formPromise: Promise<any>;
deletePromise: Promise<any>;
constructor(protected folderService: FolderService, protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService, private logService: LogService) { }
constructor(
protected folderService: FolderService,
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
private logService: LogService
) {}
async ngOnInit() {
await this.init();
async ngOnInit() {
await this.init();
}
async submit(): Promise<boolean> {
if (this.folder.name == null || this.folder.name === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("nameRequired")
);
return false;
}
async submit(): Promise<boolean> {
if (this.folder.name == null || this.folder.name === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('nameRequired'));
return false;
}
try {
const folder = await this.folderService.encrypt(this.folder);
this.formPromise = this.folderService.saveWithServer(folder);
await this.formPromise;
this.platformUtilsService.showToast('success', null,
this.i18nService.t(this.editMode ? 'editedFolder' : 'addedFolder'));
this.onSavedFolder.emit(this.folder);
return true;
} catch (e) {
this.logService.error(e);
}
return false;
try {
const folder = await this.folderService.encrypt(this.folder);
this.formPromise = this.folderService.saveWithServer(folder);
await this.formPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t(this.editMode ? "editedFolder" : "addedFolder")
);
this.onSavedFolder.emit(this.folder);
return true;
} catch (e) {
this.logService.error(e);
}
async delete(): Promise<boolean> {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('deleteFolderConfirmation'), this.i18nService.t('deleteFolder'),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
return false;
}
try {
this.deletePromise = this.folderService.deleteWithServer(this.folder.id);
await this.deletePromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedFolder'));
this.onDeletedFolder.emit(this.folder);
} catch (e) {
this.logService.error(e);
}
return true;
async delete(): Promise<boolean> {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("deleteFolderConfirmation"),
this.i18nService.t("deleteFolder"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
protected async init() {
this.editMode = this.folderId != null;
if (this.editMode) {
this.editMode = true;
this.title = this.i18nService.t('editFolder');
const folder = await this.folderService.get(this.folderId);
this.folder = await folder.decrypt();
} else {
this.title = this.i18nService.t('addFolder');
}
try {
this.deletePromise = this.folderService.deleteWithServer(this.folder.id);
await this.deletePromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedFolder"));
this.onDeletedFolder.emit(this.folder);
} catch (e) {
this.logService.error(e);
}
return true;
}
protected async init() {
this.editMode = this.folderId != null;
if (this.editMode) {
this.editMode = true;
this.title = this.i18nService.t("editFolder");
const folder = await this.folderService.get(this.folderId);
this.folder = await folder.decrypt();
} else {
this.title = this.i18nService.t("addFolder");
}
}
}

View File

@@ -1,162 +1,160 @@
import {
Directive,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { Directive, EventEmitter, Input, Output } from "@angular/core";
import { CipherType } from 'jslib-common/enums/cipherType';
import { CipherType } from "jslib-common/enums/cipherType";
import { CollectionView } from 'jslib-common/models/view/collectionView';
import { FolderView } from 'jslib-common/models/view/folderView';
import { CollectionView } from "jslib-common/models/view/collectionView";
import { FolderView } from "jslib-common/models/view/folderView";
import { TreeNode } from 'jslib-common/models/domain/treeNode';
import { TreeNode } from "jslib-common/models/domain/treeNode";
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { FolderService } from 'jslib-common/abstractions/folder.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { FolderService } from "jslib-common/abstractions/folder.service";
import { StateService } from "jslib-common/abstractions/state.service";
@Directive()
export class GroupingsComponent {
@Input() showFolders = true;
@Input() showCollections = true;
@Input() showFavorites = true;
@Input() showTrash = true;
@Input() showFolders = true;
@Input() showCollections = true;
@Input() showFavorites = true;
@Input() showTrash = true;
@Output() onAllClicked = new EventEmitter();
@Output() onFavoritesClicked = new EventEmitter();
@Output() onTrashClicked = new EventEmitter();
@Output() onCipherTypeClicked = new EventEmitter<CipherType>();
@Output() onFolderClicked = new EventEmitter<FolderView>();
@Output() onAddFolder = new EventEmitter();
@Output() onEditFolder = new EventEmitter<FolderView>();
@Output() onCollectionClicked = new EventEmitter<CollectionView>();
@Output() onAllClicked = new EventEmitter();
@Output() onFavoritesClicked = new EventEmitter();
@Output() onTrashClicked = new EventEmitter();
@Output() onCipherTypeClicked = new EventEmitter<CipherType>();
@Output() onFolderClicked = new EventEmitter<FolderView>();
@Output() onAddFolder = new EventEmitter();
@Output() onEditFolder = new EventEmitter<FolderView>();
@Output() onCollectionClicked = new EventEmitter<CollectionView>();
folders: FolderView[];
nestedFolders: TreeNode<FolderView>[];
collections: CollectionView[];
nestedCollections: TreeNode<CollectionView>[];
loaded: boolean = false;
cipherType = CipherType;
selectedAll: boolean = false;
selectedFavorites: boolean = false;
selectedTrash: boolean = false;
selectedType: CipherType = null;
selectedFolder: boolean = false;
selectedFolderId: string = null;
selectedCollectionId: string = null;
folders: FolderView[];
nestedFolders: TreeNode<FolderView>[];
collections: CollectionView[];
nestedCollections: TreeNode<CollectionView>[];
loaded: boolean = false;
cipherType = CipherType;
selectedAll: boolean = false;
selectedFavorites: boolean = false;
selectedTrash: boolean = false;
selectedType: CipherType = null;
selectedFolder: boolean = false;
selectedFolderId: string = null;
selectedCollectionId: string = null;
private collapsedGroupings: Set<string>;
private collapsedGroupings: Set<string>;
constructor(protected collectionService: CollectionService, protected folderService: FolderService,
protected stateService: StateService) { }
constructor(
protected collectionService: CollectionService,
protected folderService: FolderService,
protected stateService: StateService
) {}
async load(setLoaded = true) {
const collapsedGroupings = await this.stateService.getCollapsedGroupings();
if (collapsedGroupings == null) {
this.collapsedGroupings = new Set<string>();
} else {
this.collapsedGroupings = new Set(collapsedGroupings);
}
await this.loadFolders();
await this.loadCollections();
if (setLoaded) {
this.loaded = true;
}
async load(setLoaded = true) {
const collapsedGroupings = await this.stateService.getCollapsedGroupings();
if (collapsedGroupings == null) {
this.collapsedGroupings = new Set<string>();
} else {
this.collapsedGroupings = new Set(collapsedGroupings);
}
async loadCollections(organizationId?: string) {
if (!this.showCollections) {
return;
}
const collections = await this.collectionService.getAllDecrypted();
if (organizationId != null) {
this.collections = collections.filter(c => c.organizationId === organizationId);
} else {
this.collections = collections;
}
this.nestedCollections = await this.collectionService.getAllNested(this.collections);
}
await this.loadFolders();
await this.loadCollections();
async loadFolders() {
if (!this.showFolders) {
return;
}
this.folders = await this.folderService.getAllDecrypted();
this.nestedFolders = await this.folderService.getAllNested();
if (setLoaded) {
this.loaded = true;
}
}
selectAll() {
this.clearSelections();
this.selectedAll = true;
this.onAllClicked.emit();
async loadCollections(organizationId?: string) {
if (!this.showCollections) {
return;
}
const collections = await this.collectionService.getAllDecrypted();
if (organizationId != null) {
this.collections = collections.filter((c) => c.organizationId === organizationId);
} else {
this.collections = collections;
}
this.nestedCollections = await this.collectionService.getAllNested(this.collections);
}
selectFavorites() {
this.clearSelections();
this.selectedFavorites = true;
this.onFavoritesClicked.emit();
async loadFolders() {
if (!this.showFolders) {
return;
}
this.folders = await this.folderService.getAllDecrypted();
this.nestedFolders = await this.folderService.getAllNested();
}
selectTrash() {
this.clearSelections();
this.selectedTrash = true;
this.onTrashClicked.emit();
}
selectAll() {
this.clearSelections();
this.selectedAll = true;
this.onAllClicked.emit();
}
selectType(type: CipherType) {
this.clearSelections();
this.selectedType = type;
this.onCipherTypeClicked.emit(type);
}
selectFavorites() {
this.clearSelections();
this.selectedFavorites = true;
this.onFavoritesClicked.emit();
}
selectFolder(folder: FolderView) {
this.clearSelections();
this.selectedFolder = true;
this.selectedFolderId = folder.id;
this.onFolderClicked.emit(folder);
}
selectTrash() {
this.clearSelections();
this.selectedTrash = true;
this.onTrashClicked.emit();
}
addFolder() {
this.onAddFolder.emit();
}
selectType(type: CipherType) {
this.clearSelections();
this.selectedType = type;
this.onCipherTypeClicked.emit(type);
}
editFolder(folder: FolderView) {
this.onEditFolder.emit(folder);
}
selectFolder(folder: FolderView) {
this.clearSelections();
this.selectedFolder = true;
this.selectedFolderId = folder.id;
this.onFolderClicked.emit(folder);
}
selectCollection(collection: CollectionView) {
this.clearSelections();
this.selectedCollectionId = collection.id;
this.onCollectionClicked.emit(collection);
}
addFolder() {
this.onAddFolder.emit();
}
clearSelections() {
this.selectedAll = false;
this.selectedFavorites = false;
this.selectedTrash = false;
this.selectedType = null;
this.selectedFolder = false;
this.selectedFolderId = null;
this.selectedCollectionId = null;
}
editFolder(folder: FolderView) {
this.onEditFolder.emit(folder);
}
async collapse(grouping: FolderView | CollectionView, idPrefix = '') {
if (grouping.id == null) {
return;
}
const id = idPrefix + grouping.id;
if (this.isCollapsed(grouping, idPrefix)) {
this.collapsedGroupings.delete(id);
} else {
this.collapsedGroupings.add(id);
}
await this.stateService.setCollapsedGroupings(this.collapsedGroupings);
}
selectCollection(collection: CollectionView) {
this.clearSelections();
this.selectedCollectionId = collection.id;
this.onCollectionClicked.emit(collection);
}
isCollapsed(grouping: FolderView | CollectionView, idPrefix = '') {
return this.collapsedGroupings.has(idPrefix + grouping.id);
clearSelections() {
this.selectedAll = false;
this.selectedFavorites = false;
this.selectedTrash = false;
this.selectedType = null;
this.selectedFolder = false;
this.selectedFolderId = null;
this.selectedCollectionId = null;
}
async collapse(grouping: FolderView | CollectionView, idPrefix = "") {
if (grouping.id == null) {
return;
}
const id = idPrefix + grouping.id;
if (this.isCollapsed(grouping, idPrefix)) {
this.collapsedGroupings.delete(id);
} else {
this.collapsedGroupings.add(id);
}
await this.stateService.setCollapsedGroupings(this.collapsedGroupings);
}
isCollapsed(grouping: FolderView | CollectionView, idPrefix = "") {
return this.collapsedGroupings.has(idPrefix + grouping.id);
}
}

View File

@@ -1,46 +1,56 @@
import { Router } from '@angular/router';
import { Router } from "@angular/router";
import { PasswordHintRequest } from 'jslib-common/models/request/passwordHintRequest';
import { PasswordHintRequest } from "jslib-common/models/request/passwordHintRequest";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
export class HintComponent {
email: string = '';
formPromise: Promise<any>;
email: string = "";
formPromise: Promise<any>;
protected successRoute = 'login';
protected onSuccessfulSubmit: () => void;
protected successRoute = "login";
protected onSuccessfulSubmit: () => void;
constructor(protected router: Router, protected i18nService: I18nService,
protected apiService: ApiService, protected platformUtilsService: PlatformUtilsService,
private logService: LogService) { }
constructor(
protected router: Router,
protected i18nService: I18nService,
protected apiService: ApiService,
protected platformUtilsService: PlatformUtilsService,
private logService: LogService
) {}
async submit() {
if (this.email == null || this.email === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('emailRequired'));
return;
}
if (this.email.indexOf('@') === -1) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('invalidEmail'));
return;
}
try {
this.formPromise = this.apiService.postPasswordHint(new PasswordHintRequest(this.email));
await this.formPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('masterPassSent'));
if (this.onSuccessfulSubmit != null) {
this.onSuccessfulSubmit();
} else if (this.router != null) {
this.router.navigate([this.successRoute]);
}
} catch (e) {
this.logService.error(e);
}
async submit() {
if (this.email == null || this.email === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("emailRequired")
);
return;
}
if (this.email.indexOf("@") === -1) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("invalidEmail")
);
return;
}
try {
this.formPromise = this.apiService.postPasswordHint(new PasswordHintRequest(this.email));
await this.formPromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("masterPassSent"));
if (this.onSuccessfulSubmit != null) {
this.onSuccessfulSubmit();
} else if (this.router != null) {
this.router.navigate([this.successRoute]);
}
} catch (e) {
this.logService.error(e);
}
}
}

View File

@@ -1,4 +1,4 @@
<div class="icon" aria-hidden="true">
<img [src]="image" appFallbackSrc="{{fallbackImage}}" *ngIf="imageEnabled && image" alt="" />
<i class="fa fa-fw fa-lg {{icon}}" *ngIf="!imageEnabled || !image"></i>
<img [src]="image" appFallbackSrc="{{ fallbackImage }}" *ngIf="imageEnabled && image" alt="" />
<i class="fa fa-fw fa-lg {{ icon }}" *ngIf="!imageEnabled || !image"></i>
</div>

View File

@@ -1,105 +1,105 @@
import {
Component,
Input,
OnChanges,
} from '@angular/core';
import { Component, Input, OnChanges } from "@angular/core";
import { CipherType } from 'jslib-common/enums/cipherType';
import { CipherType } from "jslib-common/enums/cipherType";
import { CipherView } from 'jslib-common/models/view/cipherView';
import { CipherView } from "jslib-common/models/view/cipherView";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
const IconMap: any = {
'fa-globe': String.fromCharCode(0xf0ac),
'fa-sticky-note-o': String.fromCharCode(0xf24a),
'fa-id-card-o': String.fromCharCode(0xf2c3),
'fa-credit-card': String.fromCharCode(0xf09d),
'fa-android': String.fromCharCode(0xf17b),
'fa-apple': String.fromCharCode(0xf179),
"fa-globe": String.fromCharCode(0xf0ac),
"fa-sticky-note-o": String.fromCharCode(0xf24a),
"fa-id-card-o": String.fromCharCode(0xf2c3),
"fa-credit-card": String.fromCharCode(0xf09d),
"fa-android": String.fromCharCode(0xf17b),
"fa-apple": String.fromCharCode(0xf179),
};
@Component({
selector: 'app-vault-icon',
templateUrl: 'icon.component.html',
selector: "app-vault-icon",
templateUrl: "icon.component.html",
})
export class IconComponent implements OnChanges {
@Input() cipher: CipherView;
icon: string;
image: string;
fallbackImage: string;
imageEnabled: boolean;
@Input() cipher: CipherView;
icon: string;
image: string;
fallbackImage: string;
imageEnabled: boolean;
private iconsUrl: string;
private iconsUrl: string;
constructor(environmentService: EnvironmentService, private stateService: StateService) {
this.iconsUrl = environmentService.getIconsUrl();
constructor(environmentService: EnvironmentService, private stateService: StateService) {
this.iconsUrl = environmentService.getIconsUrl();
}
async ngOnChanges() {
// Components may be re-used when using cdk-virtual-scroll. Which puts the component in a weird state,
// to avoid this we reset all state variables.
this.image = null;
this.fallbackImage = null;
this.imageEnabled = !(await this.stateService.getDisableFavicon());
this.load();
}
get iconCode(): string {
return IconMap[this.icon];
}
protected load() {
switch (this.cipher.type) {
case CipherType.Login:
this.icon = "fa-globe";
this.setLoginIcon();
break;
case CipherType.SecureNote:
this.icon = "fa-sticky-note-o";
break;
case CipherType.Card:
this.icon = "fa-credit-card";
break;
case CipherType.Identity:
this.icon = "fa-id-card-o";
break;
default:
break;
}
}
async ngOnChanges() {
// Components may be re-used when using cdk-virtual-scroll. Which puts the component in a weird state,
// to avoid this we reset all state variables.
private setLoginIcon() {
if (this.cipher.login.uri) {
let hostnameUri = this.cipher.login.uri;
let isWebsite = false;
if (hostnameUri.indexOf("androidapp://") === 0) {
this.icon = "fa-android";
this.image = null;
this.fallbackImage = null;
this.imageEnabled = !(await this.stateService.getDisableFavicon());
this.load();
}
} else if (hostnameUri.indexOf("iosapp://") === 0) {
this.icon = "fa-apple";
this.image = null;
} else if (
this.imageEnabled &&
hostnameUri.indexOf("://") === -1 &&
hostnameUri.indexOf(".") > -1
) {
hostnameUri = "http://" + hostnameUri;
isWebsite = true;
} else if (this.imageEnabled) {
isWebsite = hostnameUri.indexOf("http") === 0 && hostnameUri.indexOf(".") > -1;
}
get iconCode(): string {
return IconMap[this.icon];
}
protected load() {
switch (this.cipher.type) {
case CipherType.Login:
this.icon = 'fa-globe';
this.setLoginIcon();
break;
case CipherType.SecureNote:
this.icon = 'fa-sticky-note-o';
break;
case CipherType.Card:
this.icon = 'fa-credit-card';
break;
case CipherType.Identity:
this.icon = 'fa-id-card-o';
break;
default:
break;
}
}
private setLoginIcon() {
if (this.cipher.login.uri) {
let hostnameUri = this.cipher.login.uri;
let isWebsite = false;
if (hostnameUri.indexOf('androidapp://') === 0) {
this.icon = 'fa-android';
this.image = null;
} else if (hostnameUri.indexOf('iosapp://') === 0) {
this.icon = 'fa-apple';
this.image = null;
} else if (this.imageEnabled && hostnameUri.indexOf('://') === -1 && hostnameUri.indexOf('.') > -1) {
hostnameUri = 'http://' + hostnameUri;
isWebsite = true;
} else if (this.imageEnabled) {
isWebsite = hostnameUri.indexOf('http') === 0 && hostnameUri.indexOf('.') > -1;
}
if (this.imageEnabled && isWebsite) {
try {
this.image = this.iconsUrl + '/' + Utils.getHostname(hostnameUri) + '/icon.png';
this.fallbackImage = 'images/fa-globe.png';
} catch (e) {
// Ignore error since the fallback icon will be shown if image is null.
}
}
} else {
this.image = null;
if (this.imageEnabled && isWebsite) {
try {
this.image = this.iconsUrl + "/" + Utils.getHostname(hostnameUri) + "/icon.png";
this.fallbackImage = "images/fa-globe.png";
} catch (e) {
// Ignore error since the fallback icon will be shown if image is null.
}
}
} else {
this.image = null;
}
}
}

View File

@@ -1,216 +1,279 @@
import { Directive, NgZone, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { take } from 'rxjs/operators';
import { Directive, NgZone, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { take } from "rxjs/operators";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { EncString } from 'jslib-common/models/domain/encString';
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
import { EncString } from "jslib-common/models/domain/encString";
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
import { SecretVerificationRequest } from 'jslib-common/models/request/secretVerificationRequest';
import { SecretVerificationRequest } from "jslib-common/models/request/secretVerificationRequest";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
import { HashPurpose } from 'jslib-common/enums/hashPurpose';
import { KeySuffixOptions } from 'jslib-common/enums/keySuffixOptions';
import { HashPurpose } from "jslib-common/enums/hashPurpose";
import { KeySuffixOptions } from "jslib-common/enums/keySuffixOptions";
@Directive()
export class LockComponent implements OnInit {
masterPassword: string = '';
pin: string = '';
showPassword: boolean = false;
email: string;
pinLock: boolean = false;
webVaultHostname: string = '';
formPromise: Promise<any>;
supportsBiometric: boolean;
biometricLock: boolean;
biometricText: string;
hideInput: boolean;
masterPassword: string = "";
pin: string = "";
showPassword: boolean = false;
email: string;
pinLock: boolean = false;
webVaultHostname: string = "";
formPromise: Promise<any>;
supportsBiometric: boolean;
biometricLock: boolean;
biometricText: string;
hideInput: boolean;
protected successRoute: string = 'vault';
protected onSuccessfulSubmit: () => Promise<void>;
protected successRoute: string = "vault";
protected onSuccessfulSubmit: () => Promise<void>;
private invalidPinAttempts = 0;
private pinSet: [boolean, boolean];
private invalidPinAttempts = 0;
private pinSet: [boolean, boolean];
constructor(protected router: Router, protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService, protected messagingService: MessagingService,
protected cryptoService: CryptoService, protected vaultTimeoutService: VaultTimeoutService,
protected environmentService: EnvironmentService, protected stateService: StateService,
protected apiService: ApiService, private logService: LogService,
private keyConnectorService: KeyConnectorService, protected ngZone: NgZone) { }
constructor(
protected router: Router,
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected messagingService: MessagingService,
protected cryptoService: CryptoService,
protected vaultTimeoutService: VaultTimeoutService,
protected environmentService: EnvironmentService,
protected stateService: StateService,
protected apiService: ApiService,
private logService: LogService,
private keyConnectorService: KeyConnectorService,
protected ngZone: NgZone
) {}
async ngOnInit() {
this.stateService.activeAccount.subscribe(async _userId => {
await this.load();
});
async ngOnInit() {
this.stateService.activeAccount.subscribe(async (_userId) => {
await this.load();
});
}
async submit() {
if (this.pinLock && (this.pin == null || this.pin === "")) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("pinRequired")
);
return;
}
if (!this.pinLock && (this.masterPassword == null || this.masterPassword === "")) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPassRequired")
);
return;
}
async submit() {
if (this.pinLock && (this.pin == null || this.pin === '')) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('pinRequired'));
return;
}
if (!this.pinLock && (this.masterPassword == null || this.masterPassword === '')) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassRequired'));
return;
}
const kdf = await this.stateService.getKdfType();
const kdfIterations = await this.stateService.getKdfIterations();
const kdf = await this.stateService.getKdfType();
const kdfIterations = await this.stateService.getKdfIterations();
if (this.pinLock) {
let failed = true;
try {
if (this.pinSet[0]) {
const key = await this.cryptoService.makeKeyFromPin(this.pin, this.email, kdf, kdfIterations,
await this.stateService.getDecryptedPinProtected());
const encKey = await this.cryptoService.getEncKey(key);
const protectedPin = await this.stateService.getProtectedPin();
const decPin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), encKey);
failed = decPin !== this.pin;
if (!failed) {
await this.setKeyAndContinue(key);
}
} else {
const key = await this.cryptoService.makeKeyFromPin(this.pin, this.email, kdf, kdfIterations);
failed = false;
await this.setKeyAndContinue(key);
}
} catch {
failed = true;
}
if (failed) {
this.invalidPinAttempts++;
if (this.invalidPinAttempts >= 5) {
this.messagingService.send('logout');
return;
}
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('invalidPin'));
}
if (this.pinLock) {
let failed = true;
try {
if (this.pinSet[0]) {
const key = await this.cryptoService.makeKeyFromPin(
this.pin,
this.email,
kdf,
kdfIterations,
await this.stateService.getDecryptedPinProtected()
);
const encKey = await this.cryptoService.getEncKey(key);
const protectedPin = await this.stateService.getProtectedPin();
const decPin = await this.cryptoService.decryptToUtf8(
new EncString(protectedPin),
encKey
);
failed = decPin !== this.pin;
if (!failed) {
await this.setKeyAndContinue(key);
}
} else {
const key = await this.cryptoService.makeKey(this.masterPassword, this.email, kdf, kdfIterations);
const storedKeyHash = await this.cryptoService.getKeyHash();
let passwordValid = false;
if (storedKeyHash != null) {
passwordValid = await this.cryptoService.compareAndUpdateKeyHash(this.masterPassword, key);
} else {
const request = new SecretVerificationRequest();
const serverKeyHash = await this.cryptoService.hashPassword(this.masterPassword, key,
HashPurpose.ServerAuthorization);
request.masterPasswordHash = serverKeyHash;
try {
this.formPromise = this.apiService.postAccountVerifyPassword(request);
await this.formPromise;
passwordValid = true;
const localKeyHash = await this.cryptoService.hashPassword(this.masterPassword, key,
HashPurpose.LocalAuthorization);
await this.cryptoService.setKeyHash(localKeyHash);
} catch (e) {
this.logService.error(e);
}
}
if (passwordValid) {
if (this.pinSet[0]) {
const protectedPin = await this.stateService.getProtectedPin();
const encKey = await this.cryptoService.getEncKey(key);
const decPin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), encKey);
const pinKey = await this.cryptoService.makePinKey(decPin, this.email, kdf, kdfIterations);
await this.stateService.setDecryptedPinProtected(await this.cryptoService.encrypt(key.key, pinKey));
}
await this.setKeyAndContinue(key);
} else {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('invalidMasterPassword'));
}
const key = await this.cryptoService.makeKeyFromPin(
this.pin,
this.email,
kdf,
kdfIterations
);
failed = false;
await this.setKeyAndContinue(key);
}
} catch {
failed = true;
}
if (failed) {
this.invalidPinAttempts++;
if (this.invalidPinAttempts >= 5) {
this.messagingService.send("logout");
return;
}
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("invalidPin")
);
}
} else {
const key = await this.cryptoService.makeKey(
this.masterPassword,
this.email,
kdf,
kdfIterations
);
const storedKeyHash = await this.cryptoService.getKeyHash();
let passwordValid = false;
if (storedKeyHash != null) {
passwordValid = await this.cryptoService.compareAndUpdateKeyHash(this.masterPassword, key);
} else {
const request = new SecretVerificationRequest();
const serverKeyHash = await this.cryptoService.hashPassword(
this.masterPassword,
key,
HashPurpose.ServerAuthorization
);
request.masterPasswordHash = serverKeyHash;
try {
this.formPromise = this.apiService.postAccountVerifyPassword(request);
await this.formPromise;
passwordValid = true;
const localKeyHash = await this.cryptoService.hashPassword(
this.masterPassword,
key,
HashPurpose.LocalAuthorization
);
await this.cryptoService.setKeyHash(localKeyHash);
} catch (e) {
this.logService.error(e);
}
}
if (passwordValid) {
if (this.pinSet[0]) {
const protectedPin = await this.stateService.getProtectedPin();
const encKey = await this.cryptoService.getEncKey(key);
const decPin = await this.cryptoService.decryptToUtf8(
new EncString(protectedPin),
encKey
);
const pinKey = await this.cryptoService.makePinKey(
decPin,
this.email,
kdf,
kdfIterations
);
await this.stateService.setDecryptedPinProtected(
await this.cryptoService.encrypt(key.key, pinKey)
);
}
await this.setKeyAndContinue(key);
} else {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("invalidMasterPassword")
);
}
}
}
async logOut() {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("logOutConfirmation"),
this.i18nService.t("logOut"),
this.i18nService.t("logOut"),
this.i18nService.t("cancel")
);
if (confirmed) {
this.messagingService.send("logout");
}
}
async unlockBiometric(): Promise<boolean> {
if (!this.biometricLock) {
return;
}
async logOut() {
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('logOutConfirmation'),
this.i18nService.t('logOut'), this.i18nService.t('logOut'), this.i18nService.t('cancel'));
if (confirmed) {
this.messagingService.send('logout');
}
const success = (await this.cryptoService.getKey(KeySuffixOptions.Biometric)) != null;
if (success) {
await this.doContinue();
}
async unlockBiometric(): Promise<boolean> {
if (!this.biometricLock) {
return;
}
return success;
}
const success = (await this.cryptoService.getKey(KeySuffixOptions.Biometric)) != null;
togglePassword() {
this.showPassword = !this.showPassword;
const input = document.getElementById(this.pinLock ? "pin" : "masterPassword");
if (this.ngZone.isStable) {
input.focus();
} else {
this.ngZone.onStable.pipe(take(1)).subscribe(() => input.focus());
}
}
if (success) {
await this.doContinue();
}
private async setKeyAndContinue(key: SymmetricCryptoKey) {
await this.cryptoService.setKey(key);
await this.doContinue();
}
return success;
private async doContinue() {
await this.stateService.setBiometricLocked(false);
await this.stateService.setEverBeenUnlocked(true);
const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.setDisableFavicon(!!disableFavicon);
this.messagingService.send("unlocked");
if (this.onSuccessfulSubmit != null) {
await this.onSuccessfulSubmit();
} else if (this.router != null) {
this.router.navigate([this.successRoute]);
}
}
private async load() {
this.pinSet = await this.vaultTimeoutService.isPinLockSet();
this.pinLock =
(this.pinSet[0] && (await this.stateService.getDecryptedPinProtected()) != null) ||
this.pinSet[1];
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
this.biometricLock =
(await this.vaultTimeoutService.isBiometricLockSet()) &&
((await this.cryptoService.hasKeyStored(KeySuffixOptions.Biometric)) ||
!this.platformUtilsService.supportsSecureStorage());
this.biometricText = await this.stateService.getBiometricText();
this.email = await this.stateService.getEmail();
const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
this.hideInput = usesKeyConnector && !this.pinLock;
// Users with key connector and without biometric or pin has no MP to unlock using
if (usesKeyConnector && !(this.biometricLock || this.pinLock)) {
await this.vaultTimeoutService.logOut();
}
togglePassword() {
this.showPassword = !this.showPassword;
const input = document.getElementById(this.pinLock ? 'pin' : 'masterPassword');
if (this.ngZone.isStable) {
input.focus();
} else {
this.ngZone.onStable.pipe(take(1)).subscribe(() => input.focus());
}
}
private async setKeyAndContinue(key: SymmetricCryptoKey) {
await this.cryptoService.setKey(key);
await this.doContinue();
}
private async doContinue() {
await this.stateService.setBiometricLocked(false);
await this.stateService.setEverBeenUnlocked(true);
const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.setDisableFavicon(!!disableFavicon);
this.messagingService.send('unlocked');
if (this.onSuccessfulSubmit != null) {
await this.onSuccessfulSubmit();
} else if (this.router != null) {
this.router.navigate([this.successRoute]);
}
}
private async load() {
this.pinSet = await this.vaultTimeoutService.isPinLockSet();
this.pinLock = (this.pinSet[0] && (await this.stateService.getDecryptedPinProtected()) != null) || this.pinSet[1];
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
this.biometricLock = await this.vaultTimeoutService.isBiometricLockSet() &&
(await this.cryptoService.hasKeyStored(KeySuffixOptions.Biometric) || !this.platformUtilsService.supportsSecureStorage());
this.biometricText = await this.stateService.getBiometricText();
this.email = await this.stateService.getEmail();
const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
this.hideInput = usesKeyConnector && !this.pinLock;
// Users with key connector and without biometric or pin has no MP to unlock using
if (usesKeyConnector && !(this.biometricLock || this.pinLock)) {
await this.vaultTimeoutService.logOut();
}
const webVaultUrl = this.environmentService.getWebVaultUrl();
const vaultUrl = webVaultUrl === 'https://vault.bitwarden.com' ? 'https://bitwarden.com' : webVaultUrl;
this.webVaultHostname = Utils.getHostname(vaultUrl);
}
const webVaultUrl = this.environmentService.getWebVaultUrl();
const vaultUrl =
webVaultUrl === "https://vault.bitwarden.com" ? "https://bitwarden.com" : webVaultUrl;
this.webVaultHostname = Utils.getHostname(vaultUrl);
}
}

View File

@@ -1,164 +1,186 @@
import {
Directive,
Input,
NgZone,
OnInit,
} from '@angular/core';
import { Directive, Input, NgZone, OnInit } from "@angular/core";
import { Router } from '@angular/router';
import { Router } from "@angular/router";
import { take } from 'rxjs/operators';
import { take } from "rxjs/operators";
import { AuthResult } from 'jslib-common/models/domain/authResult';
import { AuthResult } from "jslib-common/models/domain/authResult";
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
import { CaptchaProtectedComponent } from './captchaProtected.component';
import { CaptchaProtectedComponent } from "./captchaProtected.component";
@Directive()
export class LoginComponent extends CaptchaProtectedComponent implements OnInit {
@Input() email: string = '';
@Input() rememberEmail = true;
@Input() email: string = "";
@Input() rememberEmail = true;
masterPassword: string = '';
showPassword: boolean = false;
formPromise: Promise<AuthResult>;
onSuccessfulLogin: () => Promise<any>;
onSuccessfulLoginNavigate: () => Promise<any>;
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>;
onSuccessfulLoginForceResetNavigate: () => Promise<any>;
masterPassword: string = "";
showPassword: boolean = false;
formPromise: Promise<AuthResult>;
onSuccessfulLogin: () => Promise<any>;
onSuccessfulLoginNavigate: () => Promise<any>;
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>;
onSuccessfulLoginForceResetNavigate: () => Promise<any>;
protected twoFactorRoute = '2fa';
protected successRoute = 'vault';
protected forcePasswordResetRoute = 'update-temp-password';
protected twoFactorRoute = "2fa";
protected successRoute = "vault";
protected forcePasswordResetRoute = "update-temp-password";
constructor(protected authService: AuthService, protected router: Router,
platformUtilsService: PlatformUtilsService, i18nService: I18nService,
protected stateService: StateService, environmentService: EnvironmentService,
protected passwordGenerationService: PasswordGenerationService,
protected cryptoFunctionService: CryptoFunctionService, protected logService: LogService,
protected ngZone: NgZone) {
super(environmentService, i18nService, platformUtilsService);
constructor(
protected authService: AuthService,
protected router: Router,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
protected stateService: StateService,
environmentService: EnvironmentService,
protected passwordGenerationService: PasswordGenerationService,
protected cryptoFunctionService: CryptoFunctionService,
protected logService: LogService,
protected ngZone: NgZone
) {
super(environmentService, i18nService, platformUtilsService);
}
async ngOnInit() {
if (this.email == null || this.email === "") {
this.email = await this.stateService.getRememberedEmail();
if (this.email == null) {
this.email = "";
}
}
this.rememberEmail = (await this.stateService.getRememberedEmail()) != null;
if (Utils.isBrowser && !Utils.isNode) {
this.focusInput();
}
}
async submit() {
await this.setupCaptcha();
if (this.email == null || this.email === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("emailRequired")
);
return;
}
if (this.email.indexOf("@") === -1) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("invalidEmail")
);
return;
}
if (this.masterPassword == null || this.masterPassword === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPassRequired")
);
return;
}
async ngOnInit() {
if (this.email == null || this.email === '') {
this.email = await this.stateService.getRememberedEmail();
if (this.email == null) {
this.email = '';
}
}
this.rememberEmail = await this.stateService.getRememberedEmail() != null;
if (Utils.isBrowser && !Utils.isNode) {
this.focusInput();
}
}
async submit() {
await this.setupCaptcha();
if (this.email == null || this.email === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('emailRequired'));
return;
}
if (this.email.indexOf('@') === -1) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('invalidEmail'));
return;
}
if (this.masterPassword == null || this.masterPassword === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassRequired'));
return;
}
try {
this.formPromise = this.authService.logIn(this.email, this.masterPassword, this.captchaToken);
const response = await this.formPromise;
if (this.rememberEmail) {
await this.stateService.setRememberedEmail(this.email);
} else {
await this.stateService.setRememberedEmail(null);
}
if (this.handleCaptchaRequired(response)) {
return;
} else if (response.twoFactor) {
if (this.onSuccessfulLoginTwoFactorNavigate != null) {
this.onSuccessfulLoginTwoFactorNavigate();
} else {
this.router.navigate([this.twoFactorRoute]);
}
} else if (response.forcePasswordReset) {
if (this.onSuccessfulLoginForceResetNavigate != null) {
this.onSuccessfulLoginForceResetNavigate();
} else {
this.router.navigate([this.forcePasswordResetRoute]);
}
} else {
const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.setDisableFavicon(!!disableFavicon);
if (this.onSuccessfulLogin != null) {
this.onSuccessfulLogin();
}
if (this.onSuccessfulLoginNavigate != null) {
this.onSuccessfulLoginNavigate();
} else {
this.router.navigate([this.successRoute]);
}
}
} catch (e) {
this.logService.error(e);
}
}
togglePassword() {
this.showPassword = !this.showPassword;
if (this.ngZone.isStable) {
document.getElementById('masterPassword').focus();
try {
this.formPromise = this.authService.logIn(this.email, this.masterPassword, this.captchaToken);
const response = await this.formPromise;
if (this.rememberEmail) {
await this.stateService.setRememberedEmail(this.email);
} else {
await this.stateService.setRememberedEmail(null);
}
if (this.handleCaptchaRequired(response)) {
return;
} else if (response.twoFactor) {
if (this.onSuccessfulLoginTwoFactorNavigate != null) {
this.onSuccessfulLoginTwoFactorNavigate();
} else {
this.ngZone.onStable.pipe(take(1)).subscribe(() => document.getElementById('masterPassword').focus());
this.router.navigate([this.twoFactorRoute]);
}
} else if (response.forcePasswordReset) {
if (this.onSuccessfulLoginForceResetNavigate != null) {
this.onSuccessfulLoginForceResetNavigate();
} else {
this.router.navigate([this.forcePasswordResetRoute]);
}
} else {
const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.setDisableFavicon(!!disableFavicon);
if (this.onSuccessfulLogin != null) {
this.onSuccessfulLogin();
}
if (this.onSuccessfulLoginNavigate != null) {
this.onSuccessfulLoginNavigate();
} else {
this.router.navigate([this.successRoute]);
}
}
} catch (e) {
this.logService.error(e);
}
}
async launchSsoBrowser(clientId: string, ssoRedirectUri: string) {
// Generate necessary sso params
const passwordOptions: any = {
type: 'password',
length: 64,
uppercase: true,
lowercase: true,
numbers: true,
special: false,
};
const state = await this.passwordGenerationService.generatePassword(passwordOptions);
const ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, 'sha256');
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
// Save sso params
await this.stateService.setSsoState(state);
await this.stateService.setSsoCodeVerifier(ssoCodeVerifier);
// Build URI
const webUrl = this.environmentService.getWebVaultUrl();
// Launch browser
this.platformUtilsService.launchUri(webUrl + '/#/sso?clientId=' + clientId +
'&redirectUri=' + encodeURIComponent(ssoRedirectUri) +
'&state=' + state + '&codeChallenge=' + codeChallenge);
togglePassword() {
this.showPassword = !this.showPassword;
if (this.ngZone.isStable) {
document.getElementById("masterPassword").focus();
} else {
this.ngZone.onStable
.pipe(take(1))
.subscribe(() => document.getElementById("masterPassword").focus());
}
}
protected focusInput() {
document.getElementById(this.email == null || this.email === '' ? 'email' : 'masterPassword').focus();
}
async launchSsoBrowser(clientId: string, ssoRedirectUri: string) {
// Generate necessary sso params
const passwordOptions: any = {
type: "password",
length: 64,
uppercase: true,
lowercase: true,
numbers: true,
special: false,
};
const state = await this.passwordGenerationService.generatePassword(passwordOptions);
const ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256");
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
// Save sso params
await this.stateService.setSsoState(state);
await this.stateService.setSsoCodeVerifier(ssoCodeVerifier);
// Build URI
const webUrl = this.environmentService.getWebVaultUrl();
// Launch browser
this.platformUtilsService.launchUri(
webUrl +
"/#/sso?clientId=" +
clientId +
"&redirectUri=" +
encodeURIComponent(ssoRedirectUri) +
"&state=" +
state +
"&codeChallenge=" +
codeChallenge
);
}
protected focusInput() {
document
.getElementById(this.email == null || this.email === "" ? "email" : "masterPassword")
.focus();
}
}

View File

@@ -1,76 +1,80 @@
import {
AfterViewInit,
ChangeDetectorRef,
Component,
ComponentRef,
ElementRef,
OnDestroy,
Type,
ViewChild,
ViewContainerRef
} from '@angular/core';
AfterViewInit,
ChangeDetectorRef,
Component,
ComponentRef,
ElementRef,
OnDestroy,
Type,
ViewChild,
ViewContainerRef,
} from "@angular/core";
import {
ConfigurableFocusTrap,
ConfigurableFocusTrapFactory,
} from '@angular/cdk/a11y';
import { ConfigurableFocusTrap, ConfigurableFocusTrapFactory } from "@angular/cdk/a11y";
import { ModalService } from '../../services/modal.service';
import { ModalService } from "../../services/modal.service";
import { ModalRef } from './modal.ref';
import { ModalRef } from "./modal.ref";
@Component({
selector: 'app-modal',
template: '<ng-template #modalContent></ng-template>',
selector: "app-modal",
template: "<ng-template #modalContent></ng-template>",
})
export class DynamicModalComponent implements AfterViewInit, OnDestroy {
componentRef: ComponentRef<any>;
componentRef: ComponentRef<any>;
@ViewChild('modalContent', { read: ViewContainerRef, static: true }) modalContentRef: ViewContainerRef;
@ViewChild("modalContent", { read: ViewContainerRef, static: true })
modalContentRef: ViewContainerRef;
childComponentType: Type<any>;
setComponentParameters: (component: any) => void;
childComponentType: Type<any>;
setComponentParameters: (component: any) => void;
private focusTrap: ConfigurableFocusTrap;
private focusTrap: ConfigurableFocusTrap;
constructor(private modalService: ModalService, private cd: ChangeDetectorRef,
private el: ElementRef<HTMLElement>, private focusTrapFactory: ConfigurableFocusTrapFactory,
public modalRef: ModalRef) { }
constructor(
private modalService: ModalService,
private cd: ChangeDetectorRef,
private el: ElementRef<HTMLElement>,
private focusTrapFactory: ConfigurableFocusTrapFactory,
public modalRef: ModalRef
) {}
ngAfterViewInit() {
this.loadChildComponent(this.childComponentType);
if (this.setComponentParameters != null) {
this.setComponentParameters(this.componentRef.instance);
}
this.cd.detectChanges();
this.modalRef.created(this.el.nativeElement);
this.focusTrap = this.focusTrapFactory.create(this.el.nativeElement.querySelector('.modal-dialog'));
if (this.el.nativeElement.querySelector('[appAutoFocus]') == null) {
this.focusTrap.focusFirstTabbableElementWhenReady();
}
ngAfterViewInit() {
this.loadChildComponent(this.childComponentType);
if (this.setComponentParameters != null) {
this.setComponentParameters(this.componentRef.instance);
}
this.cd.detectChanges();
loadChildComponent(componentType: Type<any>) {
const componentFactory = this.modalService.resolveComponentFactory(componentType);
this.modalContentRef.clear();
this.componentRef = this.modalContentRef.createComponent(componentFactory);
this.modalRef.created(this.el.nativeElement);
this.focusTrap = this.focusTrapFactory.create(
this.el.nativeElement.querySelector(".modal-dialog")
);
if (this.el.nativeElement.querySelector("[appAutoFocus]") == null) {
this.focusTrap.focusFirstTabbableElementWhenReady();
}
}
ngOnDestroy() {
if (this.componentRef) {
this.componentRef.destroy();
}
this.focusTrap.destroy();
}
loadChildComponent(componentType: Type<any>) {
const componentFactory = this.modalService.resolveComponentFactory(componentType);
close() {
this.modalRef.close();
}
this.modalContentRef.clear();
this.componentRef = this.modalContentRef.createComponent(componentFactory);
}
getFocus() {
const autoFocusEl = this.el.nativeElement.querySelector('[appAutoFocus]') as HTMLElement;
autoFocusEl?.focus();
ngOnDestroy() {
if (this.componentRef) {
this.componentRef.destroy();
}
this.focusTrap.destroy();
}
close() {
this.modalRef.close();
}
getFocus() {
const autoFocusEl = this.el.nativeElement.querySelector("[appAutoFocus]") as HTMLElement;
autoFocusEl?.focus();
}
}

View File

@@ -1,15 +1,10 @@
import {
InjectFlags,
InjectionToken,
Injector,
Type
} from '@angular/core';
import { InjectFlags, InjectionToken, Injector, Type } from "@angular/core";
export class ModalInjector implements Injector {
constructor(private _parentInjector: Injector, private _additionalTokens: WeakMap<any, any>) {}
constructor(private _parentInjector: Injector, private _additionalTokens: WeakMap<any, any>) {}
get<T>(token: Type<T> | InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
get(token: any, notFoundValue?: any, flags?: any) {
return this._additionalTokens.get(token) ?? this._parentInjector.get<any>(token, notFoundValue);
}
get<T>(token: Type<T> | InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
get(token: any, notFoundValue?: any, flags?: any) {
return this._additionalTokens.get(token) ?? this._parentInjector.get<any>(token, notFoundValue);
}
}

View File

@@ -1,51 +1,50 @@
import { Observable, Subject } from 'rxjs';
import { first } from 'rxjs/operators';
import { Observable, Subject } from "rxjs";
import { first } from "rxjs/operators";
export class ModalRef {
onCreated: Observable<HTMLElement>; // Modal added to the DOM.
onClose: Observable<any>; // Initiated close.
onClosed: Observable<any>; // Modal was closed (Remove element from DOM)
onShow: Observable<void>; // Start showing modal
onShown: Observable<void>; // Modal is fully visible
onCreated: Observable<HTMLElement>; // Modal added to the DOM.
onClose: Observable<any>; // Initiated close.
onClosed: Observable<any>; // Modal was closed (Remove element from DOM)
onShow: Observable<void>; // Start showing modal
onShown: Observable<void>; // Modal is fully visible
private readonly _onCreated = new Subject<HTMLElement>();
private readonly _onClose = new Subject<any>();
private readonly _onClosed = new Subject<any>();
private readonly _onShow = new Subject<void>();
private readonly _onShown = new Subject<void>();
private lastResult: any;
private readonly _onCreated = new Subject<HTMLElement>();
private readonly _onClose = new Subject<any>();
private readonly _onClosed = new Subject<any>();
private readonly _onShow = new Subject<void>();
private readonly _onShown = new Subject<void>();
private lastResult: any;
constructor() {
this.onCreated = this._onCreated.asObservable();
this.onClose = this._onClose.asObservable();
this.onClosed = this._onClosed.asObservable();
this.onShow = this._onShow.asObservable();
this.onShown = this._onShow.asObservable();
}
constructor() {
this.onCreated = this._onCreated.asObservable();
this.onClose = this._onClose.asObservable();
this.onClosed = this._onClosed.asObservable();
this.onShow = this._onShow.asObservable();
this.onShown = this._onShow.asObservable();
}
show() {
this._onShow.next();
}
show() {
this._onShow.next();
}
shown() {
this._onShown.next();
}
shown() {
this._onShown.next();
}
close(result?: any) {
this.lastResult = result;
this._onClose.next(result);
}
close(result?: any) {
this.lastResult = result;
this._onClose.next(result);
}
closed() {
this._onClosed.next(this.lastResult);
}
closed() {
this._onClosed.next(this.lastResult);
}
created(el: HTMLElement) {
this._onCreated.next(el);
}
created(el: HTMLElement) {
this._onCreated.next(el);
}
onClosedPromise(): Promise<any> {
return this.onClosed.pipe(first()).toPromise();
}
onClosedPromise(): Promise<any> {
return this.onClosed.pipe(first()).toPromise();
}
}

View File

@@ -1,32 +1,38 @@
import { Directive, OnInit } from '@angular/core';
import { Directive, OnInit } from "@angular/core";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { GeneratedPasswordHistory } from 'jslib-common/models/domain/generatedPasswordHistory';
import { GeneratedPasswordHistory } from "jslib-common/models/domain/generatedPasswordHistory";
@Directive()
export class PasswordGeneratorHistoryComponent implements OnInit {
history: GeneratedPasswordHistory[] = [];
history: GeneratedPasswordHistory[] = [];
constructor(protected passwordGenerationService: PasswordGenerationService,
protected platformUtilsService: PlatformUtilsService, protected i18nService: I18nService,
private win: Window) { }
constructor(
protected passwordGenerationService: PasswordGenerationService,
protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService,
private win: Window
) {}
async ngOnInit() {
this.history = await this.passwordGenerationService.getHistory();
}
async ngOnInit() {
this.history = await this.passwordGenerationService.getHistory();
}
clear() {
this.history = [];
this.passwordGenerationService.clear();
}
clear() {
this.history = [];
this.passwordGenerationService.clear();
}
copy(password: string) {
const copyOptions = this.win != null ? { window: this.win } : null;
this.platformUtilsService.copyToClipboard(password, copyOptions);
this.platformUtilsService.showToast('info', null,
this.i18nService.t('valueCopied', this.i18nService.t('password')));
}
copy(password: string) {
const copyOptions = this.win != null ? { window: this.win } : null;
this.platformUtilsService.copyToClipboard(password, copyOptions);
this.platformUtilsService.showToast(
"info",
null,
this.i18nService.t("valueCopied", this.i18nService.t("password"))
);
}
}

View File

@@ -1,101 +1,106 @@
import {
Directive,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PasswordGeneratorPolicyOptions } from 'jslib-common/models/domain/passwordGeneratorPolicyOptions';
import { PasswordGeneratorPolicyOptions } from "jslib-common/models/domain/passwordGeneratorPolicyOptions";
@Directive()
export class PasswordGeneratorComponent implements OnInit {
@Input() showSelect: boolean = false;
@Output() onSelected = new EventEmitter<string>();
@Input() showSelect: boolean = false;
@Output() onSelected = new EventEmitter<string>();
passTypeOptions: any[];
options: any = {};
password: string = '-';
showOptions = false;
avoidAmbiguous = false;
enforcedPolicyOptions: PasswordGeneratorPolicyOptions;
passTypeOptions: any[];
options: any = {};
password: string = "-";
showOptions = false;
avoidAmbiguous = false;
enforcedPolicyOptions: PasswordGeneratorPolicyOptions;
constructor(protected passwordGenerationService: PasswordGenerationService,
protected platformUtilsService: PlatformUtilsService, protected i18nService: I18nService,
private win: Window) {
this.passTypeOptions = [
{ name: i18nService.t('password'), value: 'password' },
{ name: i18nService.t('passphrase'), value: 'passphrase' },
];
}
constructor(
protected passwordGenerationService: PasswordGenerationService,
protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService,
private win: Window
) {
this.passTypeOptions = [
{ name: i18nService.t("password"), value: "password" },
{ name: i18nService.t("passphrase"), value: "passphrase" },
];
}
async ngOnInit() {
const optionsResponse = await this.passwordGenerationService.getOptions();
this.options = optionsResponse[0];
this.enforcedPolicyOptions = optionsResponse[1];
this.avoidAmbiguous = !this.options.ambiguous;
this.options.type = this.options.type === 'passphrase' ? 'passphrase' : 'password';
this.password = await this.passwordGenerationService.generatePassword(this.options);
await this.passwordGenerationService.addHistory(this.password);
async ngOnInit() {
const optionsResponse = await this.passwordGenerationService.getOptions();
this.options = optionsResponse[0];
this.enforcedPolicyOptions = optionsResponse[1];
this.avoidAmbiguous = !this.options.ambiguous;
this.options.type = this.options.type === "passphrase" ? "passphrase" : "password";
this.password = await this.passwordGenerationService.generatePassword(this.options);
await this.passwordGenerationService.addHistory(this.password);
}
async sliderChanged() {
this.saveOptions(false);
await this.passwordGenerationService.addHistory(this.password);
}
async sliderInput() {
this.normalizeOptions();
this.password = await this.passwordGenerationService.generatePassword(this.options);
}
async saveOptions(regenerate: boolean = true) {
this.normalizeOptions();
await this.passwordGenerationService.saveOptions(this.options);
if (regenerate) {
await this.regenerate();
}
}
async sliderChanged() {
this.saveOptions(false);
await this.passwordGenerationService.addHistory(this.password);
}
async regenerate() {
this.password = await this.passwordGenerationService.generatePassword(this.options);
await this.passwordGenerationService.addHistory(this.password);
}
async sliderInput() {
this.normalizeOptions();
this.password = await this.passwordGenerationService.generatePassword(this.options);
}
copy() {
const copyOptions = this.win != null ? { window: this.win } : null;
this.platformUtilsService.copyToClipboard(this.password, copyOptions);
this.platformUtilsService.showToast(
"info",
null,
this.i18nService.t("valueCopied", this.i18nService.t("password"))
);
}
async saveOptions(regenerate: boolean = true) {
this.normalizeOptions();
await this.passwordGenerationService.saveOptions(this.options);
select() {
this.onSelected.emit(this.password);
}
if (regenerate) {
await this.regenerate();
toggleOptions() {
this.showOptions = !this.showOptions;
}
private normalizeOptions() {
// Application level normalize options depedent on class variables
this.options.ambiguous = !this.avoidAmbiguous;
if (
!this.options.uppercase &&
!this.options.lowercase &&
!this.options.number &&
!this.options.special
) {
this.options.lowercase = true;
if (this.win != null) {
const lowercase = this.win.document.querySelector("#lowercase") as HTMLInputElement;
if (lowercase) {
lowercase.checked = true;
}
}
}
async regenerate() {
this.password = await this.passwordGenerationService.generatePassword(this.options);
await this.passwordGenerationService.addHistory(this.password);
}
copy() {
const copyOptions = this.win != null ? { window: this.win } : null;
this.platformUtilsService.copyToClipboard(this.password, copyOptions);
this.platformUtilsService.showToast('info', null,
this.i18nService.t('valueCopied', this.i18nService.t('password')));
}
select() {
this.onSelected.emit(this.password);
}
toggleOptions() {
this.showOptions = !this.showOptions;
}
private normalizeOptions() {
// Application level normalize options depedent on class variables
this.options.ambiguous = !this.avoidAmbiguous;
if (!this.options.uppercase && !this.options.lowercase && !this.options.number && !this.options.special) {
this.options.lowercase = true;
if (this.win != null) {
const lowercase = this.win.document.querySelector('#lowercase') as HTMLInputElement;
if (lowercase) {
lowercase.checked = true;
}
}
}
this.passwordGenerationService.normalizeOptions(this.options, this.enforcedPolicyOptions);
}
this.passwordGenerationService.normalizeOptions(this.options, this.enforcedPolicyOptions);
}
}

View File

@@ -1,33 +1,40 @@
import { Directive, OnInit } from '@angular/core';
import { Directive, OnInit } from "@angular/core";
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PasswordHistoryView } from 'jslib-common/models/view/passwordHistoryView';
import { PasswordHistoryView } from "jslib-common/models/view/passwordHistoryView";
@Directive()
export class PasswordHistoryComponent implements OnInit {
cipherId: string;
history: PasswordHistoryView[] = [];
cipherId: string;
history: PasswordHistoryView[] = [];
constructor(protected cipherService: CipherService, protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService, private win: Window) { }
constructor(
protected cipherService: CipherService,
protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService,
private win: Window
) {}
async ngOnInit() {
await this.init();
}
async ngOnInit() {
await this.init();
}
copy(password: string) {
const copyOptions = this.win != null ? { window: this.win } : null;
this.platformUtilsService.copyToClipboard(password, copyOptions);
this.platformUtilsService.showToast('info', null,
this.i18nService.t('valueCopied', this.i18nService.t('password')));
}
copy(password: string) {
const copyOptions = this.win != null ? { window: this.win } : null;
this.platformUtilsService.copyToClipboard(password, copyOptions);
this.platformUtilsService.showToast(
"info",
null,
this.i18nService.t("valueCopied", this.i18nService.t("password"))
);
}
protected async init() {
const cipher = await this.cipherService.get(this.cipherId);
const decCipher = await cipher.decrypt();
this.history = decCipher.passwordHistory == null ? [] : decCipher.passwordHistory;
}
protected async init() {
const cipher = await this.cipherService.get(this.cipherId);
const decCipher = await cipher.decrypt();
this.history = decCipher.passwordHistory == null ? [] : decCipher.passwordHistory;
}
}

View File

@@ -1,30 +1,36 @@
import { Directive } from '@angular/core';
import { Directive } from "@angular/core";
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ModalRef } from './modal/modal.ref';
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ModalRef } from "./modal/modal.ref";
@Directive()
export class PasswordRepromptComponent {
showPassword = false;
masterPassword = "";
showPassword = false;
masterPassword = '';
constructor(
private modalRef: ModalRef,
private cryptoService: CryptoService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService
) {}
constructor(private modalRef: ModalRef, private cryptoService: CryptoService, private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService) {}
togglePassword() {
this.showPassword = !this.showPassword;
}
togglePassword() {
this.showPassword = !this.showPassword;
async submit() {
if (!(await this.cryptoService.compareAndUpdateKeyHash(this.masterPassword, null))) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("invalidMasterPassword")
);
return;
}
async submit() {
if (!await this.cryptoService.compareAndUpdateKeyHash(this.masterPassword, null)) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('invalidMasterPassword'));
return;
}
this.modalRef.close(true);
}
this.modalRef.close(true);
}
}

View File

@@ -1,49 +1,61 @@
import { Directive, OnInit } from '@angular/core';
import { Directive, OnInit } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
@Directive()
export class PremiumComponent implements OnInit {
isPremium: boolean = false;
price: number = 10;
refreshPromise: Promise<any>;
isPremium: boolean = false;
price: number = 10;
refreshPromise: Promise<any>;
constructor(protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService,
protected apiService: ApiService, private logService: LogService,
protected stateService: StateService) { }
constructor(
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected apiService: ApiService,
private logService: LogService,
protected stateService: StateService
) {}
async ngOnInit() {
this.isPremium = await this.stateService.getCanAccessPremium();
async ngOnInit() {
this.isPremium = await this.stateService.getCanAccessPremium();
}
async refresh() {
try {
this.refreshPromise = this.apiService.refreshIdentityToken();
await this.refreshPromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("refreshComplete"));
this.isPremium = await this.stateService.getCanAccessPremium();
} catch (e) {
this.logService.error(e);
}
}
async refresh() {
try {
this.refreshPromise = this.apiService.refreshIdentityToken();
await this.refreshPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('refreshComplete'));
this.isPremium = await this.stateService.getCanAccessPremium();
} catch (e) {
this.logService.error(e);
}
async purchase() {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("premiumPurchaseAlert"),
this.i18nService.t("premiumPurchase"),
this.i18nService.t("yes"),
this.i18nService.t("cancel")
);
if (confirmed) {
this.platformUtilsService.launchUri("https://vault.bitwarden.com/#/?premium=purchase");
}
}
async purchase() {
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('premiumPurchaseAlert'),
this.i18nService.t('premiumPurchase'), this.i18nService.t('yes'), this.i18nService.t('cancel'));
if (confirmed) {
this.platformUtilsService.launchUri('https://vault.bitwarden.com/#/?premium=purchase');
}
}
async manage() {
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('premiumManageAlert'),
this.i18nService.t('premiumManage'), this.i18nService.t('yes'), this.i18nService.t('cancel'));
if (confirmed) {
this.platformUtilsService.launchUri('https://vault.bitwarden.com/#/?premium=manage');
}
async manage() {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("premiumManageAlert"),
this.i18nService.t("premiumManage"),
this.i18nService.t("yes"),
this.i18nService.t("cancel")
);
if (confirmed) {
this.platformUtilsService.launchUri("https://vault.bitwarden.com/#/?premium=manage");
}
}
}

View File

@@ -1,195 +1,251 @@
import { Directive, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Directive, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { KeysRequest } from 'jslib-common/models/request/keysRequest';
import { ReferenceEventRequest } from 'jslib-common/models/request/referenceEventRequest';
import { RegisterRequest } from 'jslib-common/models/request/registerRequest';
import { KeysRequest } from "jslib-common/models/request/keysRequest";
import { ReferenceEventRequest } from "jslib-common/models/request/referenceEventRequest";
import { RegisterRequest } from "jslib-common/models/request/registerRequest";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { KdfType } from 'jslib-common/enums/kdfType';
import { KdfType } from "jslib-common/enums/kdfType";
import { CaptchaProtectedComponent } from './captchaProtected.component';
import { CaptchaProtectedComponent } from "./captchaProtected.component";
@Directive()
export class RegisterComponent extends CaptchaProtectedComponent implements OnInit {
name: string = '';
email: string = '';
masterPassword: string = '';
confirmMasterPassword: string = '';
hint: string = '';
showPassword: boolean = false;
formPromise: Promise<any>;
masterPasswordScore: number;
referenceData: ReferenceEventRequest;
showTerms = true;
acceptPolicies: boolean = false;
name: string = "";
email: string = "";
masterPassword: string = "";
confirmMasterPassword: string = "";
hint: string = "";
showPassword: boolean = false;
formPromise: Promise<any>;
masterPasswordScore: number;
referenceData: ReferenceEventRequest;
showTerms = true;
acceptPolicies: boolean = false;
protected successRoute = 'login';
private masterPasswordStrengthTimeout: any;
protected successRoute = "login";
private masterPasswordStrengthTimeout: any;
constructor(protected authService: AuthService, protected router: Router,
i18nService: I18nService, protected cryptoService: CryptoService,
protected apiService: ApiService, protected stateService: StateService,
platformUtilsService: PlatformUtilsService,
protected passwordGenerationService: PasswordGenerationService, environmentService: EnvironmentService,
protected logService: LogService) {
super(environmentService, i18nService, platformUtilsService);
this.showTerms = !platformUtilsService.isSelfHost();
constructor(
protected authService: AuthService,
protected router: Router,
i18nService: I18nService,
protected cryptoService: CryptoService,
protected apiService: ApiService,
protected stateService: StateService,
platformUtilsService: PlatformUtilsService,
protected passwordGenerationService: PasswordGenerationService,
environmentService: EnvironmentService,
protected logService: LogService
) {
super(environmentService, i18nService, platformUtilsService);
this.showTerms = !platformUtilsService.isSelfHost();
}
async ngOnInit() {
this.setupCaptcha();
}
get masterPasswordScoreWidth() {
return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
}
get masterPasswordScoreColor() {
switch (this.masterPasswordScore) {
case 4:
return "success";
case 3:
return "primary";
case 2:
return "warning";
default:
return "danger";
}
}
get masterPasswordScoreText() {
switch (this.masterPasswordScore) {
case 4:
return this.i18nService.t("strong");
case 3:
return this.i18nService.t("good");
case 2:
return this.i18nService.t("weak");
default:
return this.masterPasswordScore != null ? this.i18nService.t("weak") : null;
}
}
async submit() {
if (!this.acceptPolicies && this.showTerms) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("acceptPoliciesError")
);
return;
}
async ngOnInit() {
this.setupCaptcha();
if (this.email == null || this.email === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("emailRequired")
);
return;
}
if (this.email.indexOf("@") === -1) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("invalidEmail")
);
return;
}
if (this.masterPassword == null || this.masterPassword === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPassRequired")
);
return;
}
if (this.masterPassword.length < 8) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPassLength")
);
return;
}
if (this.masterPassword !== this.confirmMasterPassword) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPassDoesntMatch")
);
return;
}
get masterPasswordScoreWidth() {
return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
const strengthResult = this.passwordGenerationService.passwordStrength(
this.masterPassword,
this.getPasswordStrengthUserInput()
);
if (strengthResult != null && strengthResult.score < 3) {
const result = await this.platformUtilsService.showDialog(
this.i18nService.t("weakMasterPasswordDesc"),
this.i18nService.t("weakMasterPassword"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!result) {
return;
}
}
get masterPasswordScoreColor() {
switch (this.masterPasswordScore) {
case 4:
return 'success';
case 3:
return 'primary';
case 2:
return 'warning';
default:
return 'danger';
}
if (this.hint === this.masterPassword) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("hintEqualsPassword")
);
return;
}
get masterPasswordScoreText() {
switch (this.masterPasswordScore) {
case 4:
return this.i18nService.t('strong');
case 3:
return this.i18nService.t('good');
case 2:
return this.i18nService.t('weak');
default:
return this.masterPasswordScore != null ? this.i18nService.t('weak') : null;
}
this.name = this.name === "" ? null : this.name;
this.email = this.email.trim().toLowerCase();
const kdf = KdfType.PBKDF2_SHA256;
const useLowerKdf = this.platformUtilsService.isIE();
const kdfIterations = useLowerKdf ? 10000 : 100000;
const key = await this.cryptoService.makeKey(
this.masterPassword,
this.email,
kdf,
kdfIterations
);
const encKey = await this.cryptoService.makeEncKey(key);
const hashedPassword = await this.cryptoService.hashPassword(this.masterPassword, key);
const keys = await this.cryptoService.makeKeyPair(encKey[0]);
const request = new RegisterRequest(
this.email,
this.name,
hashedPassword,
this.hint,
encKey[1].encryptedString,
kdf,
kdfIterations,
this.referenceData,
this.captchaToken
);
request.keys = new KeysRequest(keys[0], keys[1].encryptedString);
const orgInvite = await this.stateService.getOrganizationInvitation();
if (orgInvite != null && orgInvite.token != null && orgInvite.organizationUserId != null) {
request.token = orgInvite.token;
request.organizationUserId = orgInvite.organizationUserId;
}
async submit() {
if (!this.acceptPolicies && this.showTerms) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('acceptPoliciesError'));
return;
}
if (this.email == null || this.email === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('emailRequired'));
return;
}
if (this.email.indexOf('@') === -1) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('invalidEmail'));
return;
}
if (this.masterPassword == null || this.masterPassword === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassRequired'));
return;
}
if (this.masterPassword.length < 8) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassLength'));
return;
}
if (this.masterPassword !== this.confirmMasterPassword) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassDoesntMatch'));
return;
}
const strengthResult = this.passwordGenerationService.passwordStrength(this.masterPassword,
this.getPasswordStrengthUserInput());
if (strengthResult != null && strengthResult.score < 3) {
const result = await this.platformUtilsService.showDialog(this.i18nService.t('weakMasterPasswordDesc'),
this.i18nService.t('weakMasterPassword'), this.i18nService.t('yes'), this.i18nService.t('no'),
'warning');
if (!result) {
return;
}
}
if (this.hint === this.masterPassword) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), this.i18nService.t('hintEqualsPassword'));
return;
}
this.name = this.name === '' ? null : this.name;
this.email = this.email.trim().toLowerCase();
const kdf = KdfType.PBKDF2_SHA256;
const useLowerKdf = this.platformUtilsService.isIE();
const kdfIterations = useLowerKdf ? 10000 : 100000;
const key = await this.cryptoService.makeKey(this.masterPassword, this.email, kdf, kdfIterations);
const encKey = await this.cryptoService.makeEncKey(key);
const hashedPassword = await this.cryptoService.hashPassword(this.masterPassword, key);
const keys = await this.cryptoService.makeKeyPair(encKey[0]);
const request = new RegisterRequest(this.email, this.name, hashedPassword,
this.hint, encKey[1].encryptedString, kdf, kdfIterations, this.referenceData, this.captchaToken);
request.keys = new KeysRequest(keys[0], keys[1].encryptedString);
const orgInvite = await this.stateService.getOrganizationInvitation();
if (orgInvite != null && orgInvite.token != null && orgInvite.organizationUserId != null) {
request.token = orgInvite.token;
request.organizationUserId = orgInvite.organizationUserId;
}
try {
this.formPromise = this.apiService.postRegister(request);
try {
await this.formPromise;
} catch (e) {
if (this.handleCaptchaRequired(e)) {
return;
} else {
throw e;
}
}
this.platformUtilsService.showToast('success', null, this.i18nService.t('newAccountCreated'));
this.router.navigate([this.successRoute], { queryParams: { email: this.email } });
} catch (e) {
this.logService.error(e);
try {
this.formPromise = this.apiService.postRegister(request);
try {
await this.formPromise;
} catch (e) {
if (this.handleCaptchaRequired(e)) {
return;
} else {
throw e;
}
}
this.platformUtilsService.showToast("success", null, this.i18nService.t("newAccountCreated"));
this.router.navigate([this.successRoute], { queryParams: { email: this.email } });
} catch (e) {
this.logService.error(e);
}
}
togglePassword(confirmField: boolean) {
this.showPassword = !this.showPassword;
document.getElementById(confirmField ? 'masterPasswordRetype' : 'masterPassword').focus();
}
togglePassword(confirmField: boolean) {
this.showPassword = !this.showPassword;
document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus();
}
updatePasswordStrength() {
if (this.masterPasswordStrengthTimeout != null) {
clearTimeout(this.masterPasswordStrengthTimeout);
}
this.masterPasswordStrengthTimeout = setTimeout(() => {
const strengthResult = this.passwordGenerationService.passwordStrength(this.masterPassword,
this.getPasswordStrengthUserInput());
this.masterPasswordScore = strengthResult == null ? null : strengthResult.score;
}, 300);
updatePasswordStrength() {
if (this.masterPasswordStrengthTimeout != null) {
clearTimeout(this.masterPasswordStrengthTimeout);
}
this.masterPasswordStrengthTimeout = setTimeout(() => {
const strengthResult = this.passwordGenerationService.passwordStrength(
this.masterPassword,
this.getPasswordStrengthUserInput()
);
this.masterPasswordScore = strengthResult == null ? null : strengthResult.score;
}, 300);
}
private getPasswordStrengthUserInput() {
let userInput: string[] = [];
const atPosition = this.email.indexOf('@');
if (atPosition > -1) {
userInput = userInput.concat(this.email.substr(0, atPosition).trim().toLowerCase().split(/[^A-Za-z0-9]/));
}
if (this.name != null && this.name !== '') {
userInput = userInput.concat(this.name.trim().toLowerCase().split(' '));
}
return userInput;
private getPasswordStrengthUserInput() {
let userInput: string[] = [];
const atPosition = this.email.indexOf("@");
if (atPosition > -1) {
userInput = userInput.concat(
this.email
.substr(0, atPosition)
.trim()
.toLowerCase()
.split(/[^A-Za-z0-9]/)
);
}
if (this.name != null && this.name !== "") {
userInput = userInput.concat(this.name.trim().toLowerCase().split(" "));
}
return userInput;
}
}

View File

@@ -1,74 +1,83 @@
import {
Directive,
OnInit,
} from '@angular/core';
import { Router } from '@angular/router';
import { Directive, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { Organization } from 'jslib-common/models/domain/organization';
import { Organization } from "jslib-common/models/domain/organization";
@Directive()
export class RemovePasswordComponent implements OnInit {
actionPromise: Promise<any>;
continuing: boolean = false;
leaving: boolean = false;
actionPromise: Promise<any>;
continuing: boolean = false;
leaving: boolean = false;
loading: boolean = true;
organization: Organization;
email: string;
loading: boolean = true;
organization: Organization;
email: string;
constructor(
private router: Router,
private stateService: StateService,
private apiService: ApiService,
private syncService: SyncService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private keyConnectorService: KeyConnectorService
) {}
constructor(private router: Router, private stateService: StateService,
private apiService: ApiService, private syncService: SyncService,
private platformUtilsService: PlatformUtilsService, private i18nService: I18nService,
private keyConnectorService: KeyConnectorService) { }
async ngOnInit() {
this.organization = await this.keyConnectorService.getManagingOrganization();
this.email = await this.stateService.getEmail();
await this.syncService.fullSync(false);
this.loading = false;
}
async ngOnInit() {
this.organization = await this.keyConnectorService.getManagingOrganization();
this.email = await this.stateService.getEmail();
await this.syncService.fullSync(false);
this.loading = false;
async convert() {
this.continuing = true;
this.actionPromise = this.keyConnectorService.migrateUser();
try {
await this.actionPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("removedMasterPassword")
);
await this.keyConnectorService.removeConvertAccountRequired();
this.router.navigate([""]);
} catch (e) {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message);
}
}
async leave() {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("leaveOrganizationConfirmation"),
this.organization.name,
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
async convert() {
this.continuing = true;
this.actionPromise = this.keyConnectorService.migrateUser();
try {
await this.actionPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('removedMasterPassword'));
await this.keyConnectorService.removeConvertAccountRequired();
this.router.navigate(['']);
} catch (e) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), e.message);
}
}
async leave() {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('leaveOrganizationConfirmation'), this.organization.name,
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
try {
this.leaving = true;
this.actionPromise = this.apiService.postLeaveOrganization(this.organization.id).then(() => {
return this.syncService.fullSync(true);
});
await this.actionPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('leftOrganization'));
await this.keyConnectorService.removeConvertAccountRequired();
this.router.navigate(['']);
} catch (e) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), e);
}
try {
this.leaving = true;
this.actionPromise = this.apiService.postLeaveOrganization(this.organization.id).then(() => {
return this.syncService.fullSync(true);
});
await this.actionPromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("leftOrganization"));
await this.keyConnectorService.removeConvertAccountRequired();
this.router.navigate([""]);
} catch (e) {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e);
}
}
}

View File

@@ -1,274 +1,296 @@
import { DatePipe } from '@angular/common';
import {
Directive,
EventEmitter,
Input,
OnInit,
Output
} from '@angular/core';
import { DatePipe } from "@angular/common";
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { PolicyType } from 'jslib-common/enums/policyType';
import { SendType } from 'jslib-common/enums/sendType';
import { PolicyType } from "jslib-common/enums/policyType";
import { SendType } from "jslib-common/enums/sendType";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SendService } from 'jslib-common/abstractions/send.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SendService } from "jslib-common/abstractions/send.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SendFileView } from 'jslib-common/models/view/sendFileView';
import { SendTextView } from 'jslib-common/models/view/sendTextView';
import { SendView } from 'jslib-common/models/view/sendView';
import { SendFileView } from "jslib-common/models/view/sendFileView";
import { SendTextView } from "jslib-common/models/view/sendTextView";
import { SendView } from "jslib-common/models/view/sendView";
import { EncArrayBuffer } from 'jslib-common/models/domain/encArrayBuffer';
import { Send } from 'jslib-common/models/domain/send';
import { EncArrayBuffer } from "jslib-common/models/domain/encArrayBuffer";
import { Send } from "jslib-common/models/domain/send";
@Directive()
export class AddEditComponent implements OnInit {
@Input() sendId: string;
@Input() type: SendType;
@Input() sendId: string;
@Input() type: SendType;
@Output() onSavedSend = new EventEmitter<SendView>();
@Output() onDeletedSend = new EventEmitter<SendView>();
@Output() onCancelled = new EventEmitter<SendView>();
@Output() onSavedSend = new EventEmitter<SendView>();
@Output() onDeletedSend = new EventEmitter<SendView>();
@Output() onCancelled = new EventEmitter<SendView>();
copyLink = false;
disableSend = false;
disableHideEmail = false;
send: SendView;
deletionDate: string;
expirationDate: string;
hasPassword: boolean;
password: string;
showPassword = false;
formPromise: Promise<any>;
deletePromise: Promise<any>;
sendType = SendType;
typeOptions: any[];
canAccessPremium = true;
emailVerified = true;
alertShown = false;
showOptions = false;
copyLink = false;
disableSend = false;
disableHideEmail = false;
send: SendView;
deletionDate: string;
expirationDate: string;
hasPassword: boolean;
password: string;
showPassword = false;
formPromise: Promise<any>;
deletePromise: Promise<any>;
sendType = SendType;
typeOptions: any[];
canAccessPremium = true;
emailVerified = true;
alertShown = false;
showOptions = false;
private sendLinkBaseUrl: string;
private sendLinkBaseUrl: string;
constructor(protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService,
protected environmentService: EnvironmentService, protected datePipe: DatePipe,
protected sendService: SendService, protected messagingService: MessagingService,
protected policyService: PolicyService, private logService: LogService,
protected stateService: StateService) {
this.typeOptions = [
{ name: i18nService.t('sendTypeFile'), value: SendType.File },
{ name: i18nService.t('sendTypeText'), value: SendType.Text },
];
this.sendLinkBaseUrl = this.environmentService.getSendUrl();
constructor(
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected environmentService: EnvironmentService,
protected datePipe: DatePipe,
protected sendService: SendService,
protected messagingService: MessagingService,
protected policyService: PolicyService,
private logService: LogService,
protected stateService: StateService
) {
this.typeOptions = [
{ name: i18nService.t("sendTypeFile"), value: SendType.File },
{ name: i18nService.t("sendTypeText"), value: SendType.Text },
];
this.sendLinkBaseUrl = this.environmentService.getSendUrl();
}
get link(): string {
if (this.send.id != null && this.send.accessId != null) {
return this.sendLinkBaseUrl + this.send.accessId + "/" + this.send.urlB64Key;
}
return null;
}
get isSafari() {
return this.platformUtilsService.isSafari();
}
get isDateTimeLocalSupported(): boolean {
return !(this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari());
}
async ngOnInit() {
await this.load();
}
get editMode(): boolean {
return this.sendId != null;
}
get title(): string {
return this.i18nService.t(this.editMode ? "editSend" : "createSend");
}
setDates(event: { deletionDate: string; expirationDate: string }) {
this.deletionDate = event.deletionDate;
this.expirationDate = event.expirationDate;
}
async load() {
this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend);
this.disableHideEmail = await this.policyService.policyAppliesToUser(
PolicyType.SendOptions,
(p) => p.data.disableHideEmail
);
this.canAccessPremium = await this.stateService.getCanAccessPremium();
this.emailVerified = await this.stateService.getEmailVerified();
if (!this.canAccessPremium || !this.emailVerified) {
this.type = SendType.Text;
}
get link(): string {
if (this.send.id != null && this.send.accessId != null) {
return this.sendLinkBaseUrl + this.send.accessId + '/' + this.send.urlB64Key;
}
return null;
if (this.send == null) {
if (this.editMode) {
const send = await this.loadSend();
this.send = await send.decrypt();
} else {
this.send = new SendView();
this.send.type = this.type == null ? SendType.File : this.type;
this.send.file = new SendFileView();
this.send.text = new SendTextView();
this.send.deletionDate = new Date();
this.send.deletionDate.setDate(this.send.deletionDate.getDate() + 7);
}
}
get isSafari() {
return this.platformUtilsService.isSafari();
this.hasPassword = this.send.password != null && this.send.password.trim() !== "";
}
async submit(): Promise<boolean> {
if (this.disableSend) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("sendDisabledWarning")
);
return false;
}
get isDateTimeLocalSupported(): boolean {
return !(this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari());
if (this.send.name == null || this.send.name === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("nameRequired")
);
return false;
}
async ngOnInit() {
await this.load();
}
get editMode(): boolean {
return this.sendId != null;
}
get title(): string {
return this.i18nService.t(
this.editMode ?
'editSend' :
'createSend'
let file: File = null;
if (this.send.type === SendType.File && !this.editMode) {
const fileEl = document.getElementById("file") as HTMLInputElement;
const files = fileEl.files;
if (files == null || files.length === 0) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("selectFile")
);
return;
}
file = files[0];
if (files[0].size > 524288000) {
// 500 MB
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("maxFileSize")
);
return;
}
}
setDates(event: {deletionDate: string, expirationDate: string}) {
this.deletionDate = event.deletionDate;
this.expirationDate = event.expirationDate;
if (this.password != null && this.password.trim() === "") {
this.password = null;
}
async load() {
this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend);
this.disableHideEmail = await this.policyService.policyAppliesToUser(PolicyType.SendOptions,
p => p.data.disableHideEmail);
this.canAccessPremium = await this.stateService.getCanAccessPremium();
this.emailVerified = await this.stateService.getEmailVerified();
if (!this.canAccessPremium || !this.emailVerified) {
this.type = SendType.Text;
this.formPromise = this.encryptSend(file).then(async (encSend) => {
const uploadPromise = this.sendService.saveWithServer(encSend);
await uploadPromise;
if (this.send.id == null) {
this.send.id = encSend[0].id;
}
if (this.send.accessId == null) {
this.send.accessId = encSend[0].accessId;
}
this.onSavedSend.emit(this.send);
if (this.copyLink && this.link != null) {
const copySuccess = await this.copyLinkToClipboard(this.link);
if (copySuccess ?? true) {
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t(this.editMode ? "editedSend" : "createdSend")
);
} else {
await this.platformUtilsService.showDialog(
this.i18nService.t(this.editMode ? "editedSend" : "createdSend"),
null,
this.i18nService.t("ok"),
null,
"success",
null
);
await this.copyLinkToClipboard(this.link);
}
}
});
try {
await this.formPromise;
return true;
} catch (e) {
this.logService.error(e);
}
return false;
}
if (this.send == null) {
if (this.editMode) {
const send = await this.loadSend();
this.send = await send.decrypt();
} else {
this.send = new SendView();
this.send.type = this.type == null ? SendType.File : this.type;
this.send.file = new SendFileView();
this.send.text = new SendTextView();
this.send.deletionDate = new Date();
this.send.deletionDate.setDate(this.send.deletionDate.getDate() + 7);
}
}
async copyLinkToClipboard(link: string): Promise<void | boolean> {
return Promise.resolve(this.platformUtilsService.copyToClipboard(link));
}
this.hasPassword = this.send.password != null && this.send.password.trim() !== '';
async delete(): Promise<boolean> {
if (this.deletePromise != null) {
return false;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("deleteSendConfirmation"),
this.i18nService.t("deleteSend"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
async submit(): Promise<boolean> {
if (this.disableSend) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('sendDisabledWarning'));
return false;
}
if (this.send.name == null || this.send.name === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('nameRequired'));
return false;
}
let file: File = null;
if (this.send.type === SendType.File && !this.editMode) {
const fileEl = document.getElementById('file') as HTMLInputElement;
const files = fileEl.files;
if (files == null || files.length === 0) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('selectFile'));
return;
}
file = files[0];
if (files[0].size > 524288000) { // 500 MB
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('maxFileSize'));
return;
}
}
if (this.password != null && this.password.trim() === '') {
this.password = null;
}
this.formPromise = this.encryptSend(file)
.then(async encSend => {
const uploadPromise = this.sendService.saveWithServer(encSend);
await uploadPromise;
if (this.send.id == null) {
this.send.id = encSend[0].id;
}
if (this.send.accessId == null) {
this.send.accessId = encSend[0].accessId;
}
this.onSavedSend.emit(this.send);
if (this.copyLink && this.link != null) {
const copySuccess = await this.copyLinkToClipboard(this.link);
if (copySuccess ?? true) {
this.platformUtilsService.showToast('success', null,
this.i18nService.t(this.editMode ? 'editedSend' : 'createdSend'));
} else {
await this.platformUtilsService.showDialog(
this.i18nService.t(this.editMode ? 'editedSend' : 'createdSend'), null,
this.i18nService.t('ok'), null, 'success', null);
await this.copyLinkToClipboard(this.link);
}
}
});
try {
await this.formPromise;
return true;
} catch (e) {
this.logService.error(e);
}
return false;
try {
this.deletePromise = this.sendService.deleteWithServer(this.send.id);
await this.deletePromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedSend"));
await this.load();
this.onDeletedSend.emit(this.send);
return true;
} catch (e) {
this.logService.error(e);
}
async copyLinkToClipboard(link: string): Promise<void | boolean> {
return Promise.resolve(this.platformUtilsService.copyToClipboard(link));
return false;
}
typeChanged() {
if (this.send.type === SendType.File && !this.alertShown) {
if (!this.canAccessPremium) {
this.alertShown = true;
this.messagingService.send("premiumRequired");
} else if (!this.emailVerified) {
this.alertShown = true;
this.messagingService.send("emailVerificationRequired");
}
}
}
toggleOptions() {
this.showOptions = !this.showOptions;
}
protected async loadSend(): Promise<Send> {
return this.sendService.get(this.sendId);
}
protected async encryptSend(file: File): Promise<[Send, EncArrayBuffer]> {
const sendData = await this.sendService.encrypt(this.send, file, this.password, null);
// Parse dates
try {
sendData[0].deletionDate = this.deletionDate == null ? null : new Date(this.deletionDate);
} catch {
sendData[0].deletionDate = null;
}
try {
sendData[0].expirationDate =
this.expirationDate == null ? null : new Date(this.expirationDate);
} catch {
sendData[0].expirationDate = null;
}
async delete(): Promise<boolean> {
if (this.deletePromise != null) {
return false;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('deleteSendConfirmation'),
this.i18nService.t('deleteSend'),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
return sendData;
}
try {
this.deletePromise = this.sendService.deleteWithServer(this.send.id);
await this.deletePromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedSend'));
await this.load();
this.onDeletedSend.emit(this.send);
return true;
} catch (e) {
this.logService.error(e);
}
return false;
}
typeChanged() {
if (this.send.type === SendType.File && !this.alertShown) {
if (!this.canAccessPremium) {
this.alertShown = true;
this.messagingService.send('premiumRequired');
} else if (!this.emailVerified) {
this.alertShown = true;
this.messagingService.send('emailVerificationRequired');
}
}
}
toggleOptions() {
this.showOptions = !this.showOptions;
}
protected async loadSend(): Promise<Send> {
return this.sendService.get(this.sendId);
}
protected async encryptSend(file: File): Promise<[Send, EncArrayBuffer]> {
const sendData = await this.sendService.encrypt(this.send, file, this.password, null);
// Parse dates
try {
sendData[0].deletionDate = this.deletionDate == null ? null : new Date(this.deletionDate);
} catch {
sendData[0].deletionDate = null;
}
try {
sendData[0].expirationDate = this.expirationDate == null ? null : new Date(this.expirationDate);
} catch {
sendData[0].expirationDate = null;
}
return sendData;
}
protected togglePasswordVisible() {
this.showPassword = !this.showPassword;
document.getElementById('password').focus();
}
protected togglePasswordVisible() {
this.showPassword = !this.showPassword;
document.getElementById("password").focus();
}
}

View File

@@ -1,341 +1,354 @@
import { DatePipe } from '@angular/common';
import {
Directive,
EventEmitter,
Input,
OnInit,
Output
} from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { DatePipe } from "@angular/common";
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { FormControl, FormGroup } from "@angular/forms";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
// Different BrowserPath = different controls.
enum BrowserPath {
// Native datetime-locale.
// We are happy.
Default = 'default',
// Native datetime-locale.
// We are happy.
Default = "default",
// Native date and time inputs, but no datetime-locale.
// We use individual date and time inputs and create a datetime programatically on submit.
Firefox = 'firefox',
// Native date and time inputs, but no datetime-locale.
// We use individual date and time inputs and create a datetime programatically on submit.
Firefox = "firefox",
// No native date, time, or datetime-locale inputs.
// We use a polyfill for dates and a dropdown for times.
Safari = 'safari',
// No native date, time, or datetime-locale inputs.
// We use a polyfill for dates and a dropdown for times.
Safari = "safari",
}
enum DateField {
DeletionDate = 'deletion',
ExpriationDate = 'expiration',
DeletionDate = "deletion",
ExpriationDate = "expiration",
}
// Value = hours
enum DatePreset {
OneHour = 1,
OneDay = 24,
TwoDays = 48,
ThreeDays = 72,
SevenDays = 168,
ThirtyDays = 720,
Custom = 0,
Never = null,
OneHour = 1,
OneDay = 24,
TwoDays = 48,
ThreeDays = 72,
SevenDays = 168,
ThirtyDays = 720,
Custom = 0,
Never = null,
}
// TimeOption is used for the dropdown implementation of custom times
// twelveHour = displayed time; twentyFourHour = time used in logic
interface TimeOption {
twelveHour: string;
twentyFourHour: string;
twelveHour: string;
twentyFourHour: string;
}
@Directive()
export class EffluxDatesComponent implements OnInit {
@Input() readonly initialDeletionDate: Date;
@Input() readonly initialExpirationDate: Date;
@Input() readonly editMode: boolean;
@Input() readonly disabled: boolean;
@Input() readonly initialDeletionDate: Date;
@Input() readonly initialExpirationDate: Date;
@Input() readonly editMode: boolean;
@Input() readonly disabled: boolean;
@Output() datesChanged = new EventEmitter<{deletionDate: string, expirationDate: string}>();
@Output() datesChanged = new EventEmitter<{ deletionDate: string; expirationDate: string }>();
get browserPath(): BrowserPath {
if (this.platformUtilsService.isFirefox()) {
return BrowserPath.Firefox;
} else if (this.platformUtilsService.isSafari()) {
return BrowserPath.Safari;
}
return BrowserPath.Default;
get browserPath(): BrowserPath {
if (this.platformUtilsService.isFirefox()) {
return BrowserPath.Firefox;
} else if (this.platformUtilsService.isSafari()) {
return BrowserPath.Safari;
}
return BrowserPath.Default;
}
datesForm = new FormGroup({
selectedDeletionDatePreset: new FormControl(),
selectedExpirationDatePreset: new FormControl(),
defaultDeletionDateTime: new FormControl(),
defaultExpirationDateTime: new FormControl(),
fallbackDeletionDate: new FormControl(),
fallbackDeletionTime: new FormControl(),
fallbackExpirationDate: new FormControl(),
fallbackExpirationTime: new FormControl(),
});
datesForm = new FormGroup({
selectedDeletionDatePreset: new FormControl(),
selectedExpirationDatePreset: new FormControl(),
defaultDeletionDateTime: new FormControl(),
defaultExpirationDateTime: new FormControl(),
fallbackDeletionDate: new FormControl(),
fallbackDeletionTime: new FormControl(),
fallbackExpirationDate: new FormControl(),
fallbackExpirationTime: new FormControl(),
});
deletionDatePresets: any[] = [
{ name: this.i18nService.t('oneHour'), value: DatePreset.OneHour },
{ name: this.i18nService.t('oneDay'), value: DatePreset.OneDay },
{ name: this.i18nService.t('days', '2'), value: DatePreset.TwoDays },
{ name: this.i18nService.t('days', '3'), value: DatePreset.ThreeDays },
{ name: this.i18nService.t('days', '7'), value: DatePreset.SevenDays },
{ name: this.i18nService.t('days', '30'), value: DatePreset.ThirtyDays },
{ name: this.i18nService.t('custom'), value: DatePreset.Custom },
];
deletionDatePresets: any[] = [
{ name: this.i18nService.t("oneHour"), value: DatePreset.OneHour },
{ name: this.i18nService.t("oneDay"), value: DatePreset.OneDay },
{ name: this.i18nService.t("days", "2"), value: DatePreset.TwoDays },
{ name: this.i18nService.t("days", "3"), value: DatePreset.ThreeDays },
{ name: this.i18nService.t("days", "7"), value: DatePreset.SevenDays },
{ name: this.i18nService.t("days", "30"), value: DatePreset.ThirtyDays },
{ name: this.i18nService.t("custom"), value: DatePreset.Custom },
];
expirationDatePresets: any[] = [
{ name: this.i18nService.t('never'), value: DatePreset.Never },
].concat([...this.deletionDatePresets]);
expirationDatePresets: any[] = [
{ name: this.i18nService.t("never"), value: DatePreset.Never },
].concat([...this.deletionDatePresets]);
get selectedDeletionDatePreset(): FormControl {
return this.datesForm.get('selectedDeletionDatePreset') as FormControl;
}
get selectedDeletionDatePreset(): FormControl {
return this.datesForm.get("selectedDeletionDatePreset") as FormControl;
}
get selectedExpirationDatePreset(): FormControl {
return this.datesForm.get('selectedExpirationDatePreset') as FormControl;
}
get selectedExpirationDatePreset(): FormControl {
return this.datesForm.get("selectedExpirationDatePreset") as FormControl;
}
get defaultDeletionDateTime(): FormControl {
return this.datesForm.get('defaultDeletionDateTime') as FormControl;
}
get defaultDeletionDateTime(): FormControl {
return this.datesForm.get("defaultDeletionDateTime") as FormControl;
}
get defaultExpirationDateTime(): FormControl {
return this.datesForm.get('defaultExpirationDateTime') as FormControl;
}
get defaultExpirationDateTime(): FormControl {
return this.datesForm.get("defaultExpirationDateTime") as FormControl;
}
get fallbackDeletionDate(): FormControl {
return this.datesForm.get('fallbackDeletionDate') as FormControl;
}
get fallbackDeletionDate(): FormControl {
return this.datesForm.get("fallbackDeletionDate") as FormControl;
}
get fallbackDeletionTime(): FormControl {
return this.datesForm.get('fallbackDeletionTime') as FormControl;
}
get fallbackDeletionTime(): FormControl {
return this.datesForm.get("fallbackDeletionTime") as FormControl;
}
get fallbackExpirationDate(): FormControl {
return this.datesForm.get('fallbackExpirationDate') as FormControl;
}
get fallbackExpirationDate(): FormControl {
return this.datesForm.get("fallbackExpirationDate") as FormControl;
}
get fallbackExpirationTime(): FormControl {
return this.datesForm.get('fallbackExpirationTime') as FormControl;
}
get fallbackExpirationTime(): FormControl {
return this.datesForm.get("fallbackExpirationTime") as FormControl;
}
// Should be able to call these at any time and compute a submitable value
get formattedDeletionDate(): string {
switch (this.selectedDeletionDatePreset.value as DatePreset) {
case DatePreset.Never:
this.selectedDeletionDatePreset.setValue(DatePreset.SevenDays);
return this.formattedDeletionDate;
case DatePreset.Custom:
switch (this.browserPath) {
case BrowserPath.Safari:
case BrowserPath.Firefox:
return this.fallbackDeletionDate.value + 'T' + this.fallbackDeletionTime.value;
default:
return this.defaultDeletionDateTime.value;
}
default:
const now = new Date();
const miliseconds = now.setTime(now.getTime() +
(this.selectedDeletionDatePreset.value as number * 60 * 60 * 1000)) ;
return new Date(miliseconds).toString();
}
}
get formattedExpirationDate(): string {
switch (this.selectedExpirationDatePreset.value as DatePreset) {
case DatePreset.Never:
return null;
case DatePreset.Custom:
switch (this.browserPath) {
case BrowserPath.Safari:
case BrowserPath.Firefox:
if ((!this.fallbackExpirationDate.value || !this.fallbackExpirationTime.value) &&
this.editMode) {
return null;
}
return this.fallbackExpirationDate.value + 'T' + this.fallbackExpirationTime.value;
default:
if (!this.defaultExpirationDateTime.value) {
return null;
}
return this.defaultExpirationDateTime.value;
}
default:
const now = new Date();
const miliseconds = now.setTime(now.getTime() +
(this.selectedExpirationDatePreset.value as number * 60 * 60 * 1000));
return new Date(miliseconds).toString();
}
}
//
get safariDeletionTimePresetOptions() {
return this.safariTimePresetOptions(DateField.DeletionDate);
}
get safariExpirationTimePresetOptions() {
return this.safariTimePresetOptions(DateField.ExpriationDate);
}
private get nextWeek(): Date {
const nextWeek = new Date();
nextWeek.setDate(nextWeek.getDate() + 7);
return nextWeek;
}
constructor(protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService,
protected datePipe: DatePipe) {
}
ngOnInit(): void {
this.setInitialFormValues();
this.emitDates();
this.datesForm.valueChanges.subscribe(() => {
this.emitDates();
});
}
onDeletionDatePresetSelect(value: DatePreset) {
this.selectedDeletionDatePreset.setValue(value);
}
clearExpiration() {
// Should be able to call these at any time and compute a submitable value
get formattedDeletionDate(): string {
switch (this.selectedDeletionDatePreset.value as DatePreset) {
case DatePreset.Never:
this.selectedDeletionDatePreset.setValue(DatePreset.SevenDays);
return this.formattedDeletionDate;
case DatePreset.Custom:
switch (this.browserPath) {
case BrowserPath.Safari:
case BrowserPath.Firefox:
this.fallbackExpirationDate.setValue(null);
this.fallbackExpirationTime.setValue(null);
break;
case BrowserPath.Default:
this.defaultExpirationDateTime.setValue(null);
break;
case BrowserPath.Safari:
case BrowserPath.Firefox:
return this.fallbackDeletionDate.value + "T" + this.fallbackDeletionTime.value;
default:
return this.defaultDeletionDateTime.value;
}
default:
const now = new Date();
const miliseconds = now.setTime(
now.getTime() + (this.selectedDeletionDatePreset.value as number) * 60 * 60 * 1000
);
return new Date(miliseconds).toString();
}
}
protected emitDates() {
this.datesChanged.emit({
deletionDate: this.formattedDeletionDate,
expirationDate: this.formattedExpirationDate,
});
}
protected setInitialFormValues() {
if (this.editMode) {
this.selectedDeletionDatePreset.setValue(DatePreset.Custom);
this.selectedExpirationDatePreset.setValue(DatePreset.Custom);
switch (this.browserPath) {
case BrowserPath.Safari:
case BrowserPath.Firefox:
this.fallbackDeletionDate.setValue(this.initialDeletionDate.toISOString().slice(0, 10));
this.fallbackDeletionTime.setValue(this.initialDeletionDate.toTimeString().slice(0, 5));
if (this.initialExpirationDate != null) {
this.fallbackExpirationDate.setValue(this.initialExpirationDate.toISOString().slice(0, 10));
this.fallbackExpirationTime.setValue(this.initialExpirationDate.toTimeString().slice(0, 5));
}
break;
case BrowserPath.Default:
if (this.initialExpirationDate) {
this.defaultExpirationDateTime.setValue(
this.datePipe.transform(new Date(this.initialExpirationDate), 'yyyy-MM-ddTHH:mm'));
}
this.defaultDeletionDateTime.setValue(this.datePipe.transform(new Date(this.initialDeletionDate), 'yyyy-MM-ddTHH:mm'));
break;
get formattedExpirationDate(): string {
switch (this.selectedExpirationDatePreset.value as DatePreset) {
case DatePreset.Never:
return null;
case DatePreset.Custom:
switch (this.browserPath) {
case BrowserPath.Safari:
case BrowserPath.Firefox:
if (
(!this.fallbackExpirationDate.value || !this.fallbackExpirationTime.value) &&
this.editMode
) {
return null;
}
return this.fallbackExpirationDate.value + "T" + this.fallbackExpirationTime.value;
default:
if (!this.defaultExpirationDateTime.value) {
return null;
}
return this.defaultExpirationDateTime.value;
}
default:
const now = new Date();
const miliseconds = now.setTime(
now.getTime() + (this.selectedExpirationDatePreset.value as number) * 60 * 60 * 1000
);
return new Date(miliseconds).toString();
}
}
//
get safariDeletionTimePresetOptions() {
return this.safariTimePresetOptions(DateField.DeletionDate);
}
get safariExpirationTimePresetOptions() {
return this.safariTimePresetOptions(DateField.ExpriationDate);
}
private get nextWeek(): Date {
const nextWeek = new Date();
nextWeek.setDate(nextWeek.getDate() + 7);
return nextWeek;
}
constructor(
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected datePipe: DatePipe
) {}
ngOnInit(): void {
this.setInitialFormValues();
this.emitDates();
this.datesForm.valueChanges.subscribe(() => {
this.emitDates();
});
}
onDeletionDatePresetSelect(value: DatePreset) {
this.selectedDeletionDatePreset.setValue(value);
}
clearExpiration() {
switch (this.browserPath) {
case BrowserPath.Safari:
case BrowserPath.Firefox:
this.fallbackExpirationDate.setValue(null);
this.fallbackExpirationTime.setValue(null);
break;
case BrowserPath.Default:
this.defaultExpirationDateTime.setValue(null);
break;
}
}
protected emitDates() {
this.datesChanged.emit({
deletionDate: this.formattedDeletionDate,
expirationDate: this.formattedExpirationDate,
});
}
protected setInitialFormValues() {
if (this.editMode) {
this.selectedDeletionDatePreset.setValue(DatePreset.Custom);
this.selectedExpirationDatePreset.setValue(DatePreset.Custom);
switch (this.browserPath) {
case BrowserPath.Safari:
case BrowserPath.Firefox:
this.fallbackDeletionDate.setValue(this.initialDeletionDate.toISOString().slice(0, 10));
this.fallbackDeletionTime.setValue(this.initialDeletionDate.toTimeString().slice(0, 5));
if (this.initialExpirationDate != null) {
this.fallbackExpirationDate.setValue(
this.initialExpirationDate.toISOString().slice(0, 10)
);
this.fallbackExpirationTime.setValue(
this.initialExpirationDate.toTimeString().slice(0, 5)
);
}
break;
case BrowserPath.Default:
if (this.initialExpirationDate) {
this.defaultExpirationDateTime.setValue(
this.datePipe.transform(new Date(this.initialExpirationDate), "yyyy-MM-ddTHH:mm")
);
}
this.defaultDeletionDateTime.setValue(
this.datePipe.transform(new Date(this.initialDeletionDate), "yyyy-MM-ddTHH:mm")
);
break;
}
} else {
this.selectedDeletionDatePreset.setValue(DatePreset.SevenDays);
this.selectedExpirationDatePreset.setValue(DatePreset.Never);
switch (this.browserPath) {
case BrowserPath.Safari:
this.fallbackDeletionDate.setValue(this.nextWeek.toISOString().slice(0, 10));
this.fallbackDeletionTime.setValue(
this.safariTimePresetOptions(DateField.DeletionDate)[1].twentyFourHour
);
break;
default:
break;
}
}
}
protected safariTimePresetOptions(field: DateField): TimeOption[] {
// init individual arrays for major sort groups
const noon: TimeOption[] = [];
const midnight: TimeOption[] = [];
const ams: TimeOption[] = [];
const pms: TimeOption[] = [];
// determine minute skip (5 min, 10 min, 15 min, etc.)
const minuteIncrementer = 15;
// loop through each hour on a 12 hour system
for (let h = 1; h <= 12; h++) {
// loop through each minute in the hour using the skip to incriment
for (let m = 0; m < 60; m += minuteIncrementer) {
// init the final strings that will be added to the lists
let hour = h.toString();
let minutes = m.toString();
// add prepending 0s to single digit hours/minutes
if (h < 10) {
hour = "0" + hour;
}
if (m < 10) {
minutes = "0" + minutes;
}
// build time strings and push to relevant sort groups
if (h === 12) {
const midnightOption: TimeOption = {
twelveHour: `${hour}:${minutes} AM`,
twentyFourHour: `00:${minutes}`,
};
midnight.push(midnightOption);
const noonOption: TimeOption = {
twelveHour: `${hour}:${minutes} PM`,
twentyFourHour: `${hour}:${minutes}`,
};
noon.push(noonOption);
} else {
this.selectedDeletionDatePreset.setValue(DatePreset.SevenDays);
this.selectedExpirationDatePreset.setValue(DatePreset.Never);
const amOption: TimeOption = {
twelveHour: `${hour}:${minutes} AM`,
twentyFourHour: `${hour}:${minutes}`,
};
ams.push(amOption);
switch (this.browserPath) {
case BrowserPath.Safari:
this.fallbackDeletionDate.setValue(this.nextWeek.toISOString().slice(0, 10));
this.fallbackDeletionTime.setValue(this.safariTimePresetOptions(DateField.DeletionDate)[1].twentyFourHour);
break;
default:
break;
}
const pmOption: TimeOption = {
twelveHour: `${hour}:${minutes} PM`,
twentyFourHour: `${h + 12}:${minutes}`,
};
pms.push(pmOption);
}
}
}
protected safariTimePresetOptions(field: DateField): TimeOption[] {
// init individual arrays for major sort groups
const noon: TimeOption[] = [];
const midnight: TimeOption[] = [];
const ams: TimeOption[] = [];
const pms: TimeOption[] = [];
// bring all the arrays together in the right order
const validTimes = [...midnight, ...ams, ...noon, ...pms];
// determine minute skip (5 min, 10 min, 15 min, etc.)
const minuteIncrementer = 15;
// loop through each hour on a 12 hour system
for (let h = 1; h <= 12; h++) {
// loop through each minute in the hour using the skip to incriment
for (let m = 0; m < 60; m += minuteIncrementer) {
// init the final strings that will be added to the lists
let hour = h.toString();
let minutes = m.toString();
// add prepending 0s to single digit hours/minutes
if (h < 10) {
hour = '0' + hour;
}
if (m < 10) {
minutes = '0' + minutes;
}
// build time strings and push to relevant sort groups
if (h === 12) {
const midnightOption: TimeOption = {
twelveHour: `${hour}:${minutes} AM`,
twentyFourHour: `00:${minutes}`,
};
midnight.push(midnightOption);
const noonOption: TimeOption = {
twelveHour: `${hour}:${minutes} PM`,
twentyFourHour: `${hour}:${minutes}`,
};
noon.push(noonOption);
} else {
const amOption: TimeOption = {
twelveHour: `${hour}:${minutes} AM`,
twentyFourHour: `${hour}:${minutes}`,
};
ams.push(amOption);
const pmOption: TimeOption = {
twelveHour: `${hour}:${minutes} PM`,
twentyFourHour: `${h + 12}:${minutes}`,
};
pms.push(pmOption);
}
}
}
// bring all the arrays together in the right order
const validTimes = [...midnight, ...ams, ...noon, ...pms];
// determine if an unsupported value already exists on the send & add that to the top of the option list
// example: if the Send was created with a different client
if (field === DateField.ExpriationDate && this.initialExpirationDate != null && this.editMode) {
const previousValue: TimeOption = {
twelveHour: this.datePipe.transform(this.initialExpirationDate, 'hh:mm a'),
twentyFourHour: this.datePipe.transform(this.initialExpirationDate, 'HH:mm'),
};
return [previousValue, { twelveHour: null, twentyFourHour: null }, ...validTimes];
} else if (field === DateField.DeletionDate && this.initialDeletionDate != null && this.editMode) {
const previousValue: TimeOption = {
twelveHour: this.datePipe.transform(this.initialDeletionDate, 'hh:mm a'),
twentyFourHour: this.datePipe.transform(this.initialDeletionDate, 'HH:mm'),
};
return [previousValue, ...validTimes];
} else {
return [{ twelveHour: null, twentyFourHour: null }, ...validTimes];
}
// determine if an unsupported value already exists on the send & add that to the top of the option list
// example: if the Send was created with a different client
if (field === DateField.ExpriationDate && this.initialExpirationDate != null && this.editMode) {
const previousValue: TimeOption = {
twelveHour: this.datePipe.transform(this.initialExpirationDate, "hh:mm a"),
twentyFourHour: this.datePipe.transform(this.initialExpirationDate, "HH:mm"),
};
return [previousValue, { twelveHour: null, twentyFourHour: null }, ...validTimes];
} else if (
field === DateField.DeletionDate &&
this.initialDeletionDate != null &&
this.editMode
) {
const previousValue: TimeOption = {
twelveHour: this.datePipe.transform(this.initialDeletionDate, "hh:mm a"),
twentyFourHour: this.datePipe.transform(this.initialDeletionDate, "HH:mm"),
};
return [previousValue, ...validTimes];
} else {
return [{ twelveHour: null, twentyFourHour: null }, ...validTimes];
}
}
}

View File

@@ -1,201 +1,212 @@
import {
Directive,
NgZone,
OnInit,
} from '@angular/core';
import { Directive, NgZone, OnInit } from "@angular/core";
import { PolicyType } from 'jslib-common/enums/policyType';
import { SendType } from 'jslib-common/enums/sendType';
import { PolicyType } from "jslib-common/enums/policyType";
import { SendType } from "jslib-common/enums/sendType";
import { SendView } from 'jslib-common/models/view/sendView';
import { SendView } from "jslib-common/models/view/sendView";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SearchService } from 'jslib-common/abstractions/search.service';
import { SendService } from 'jslib-common/abstractions/send.service';
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SearchService } from "jslib-common/abstractions/search.service";
import { SendService } from "jslib-common/abstractions/send.service";
@Directive()
export class SendComponent implements OnInit {
disableSend = false;
sendType = SendType;
loaded = false;
loading = true;
refreshing = false;
expired: boolean = false;
type: SendType = null;
sends: SendView[] = [];
filteredSends: SendView[] = [];
searchText: string;
selectedType: SendType;
selectedAll: boolean;
searchPlaceholder: string;
filter: (cipher: SendView) => boolean;
searchPending = false;
hasSearched = false; // search() function called - returns true if text qualifies for search
disableSend = false;
sendType = SendType;
loaded = false;
loading = true;
refreshing = false;
expired: boolean = false;
type: SendType = null;
sends: SendView[] = [];
filteredSends: SendView[] = [];
searchText: string;
selectedType: SendType;
selectedAll: boolean;
searchPlaceholder: string;
filter: (cipher: SendView) => boolean;
searchPending = false;
hasSearched = false; // search() function called - returns true if text qualifies for search
actionPromise: any;
onSuccessfulRemovePassword: () => Promise<any>;
onSuccessfulDelete: () => Promise<any>;
onSuccessfulLoad: () => Promise<any>;
actionPromise: any;
onSuccessfulRemovePassword: () => Promise<any>;
onSuccessfulDelete: () => Promise<any>;
onSuccessfulLoad: () => Promise<any>;
private searchTimeout: any;
private searchTimeout: any;
constructor(
protected sendService: SendService,
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected environmentService: EnvironmentService,
protected ngZone: NgZone,
protected searchService: SearchService,
protected policyService: PolicyService,
private logService: LogService
) {}
constructor(protected sendService: SendService, protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService, protected environmentService: EnvironmentService,
protected ngZone: NgZone, protected searchService: SearchService,
protected policyService: PolicyService, private logService: LogService) { }
async ngOnInit() {
this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend);
}
async ngOnInit() {
this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend);
async load(filter: (send: SendView) => boolean = null) {
this.loading = true;
const sends = await this.sendService.getAllDecrypted();
this.sends = sends;
if (this.onSuccessfulLoad != null) {
await this.onSuccessfulLoad();
} else {
// Default action
this.selectAll();
}
this.loading = false;
this.loaded = true;
}
async reload(filter: (send: SendView) => boolean = null) {
this.loaded = false;
this.sends = [];
await this.load(filter);
}
async refresh() {
try {
this.refreshing = true;
await this.reload(this.filter);
} finally {
this.refreshing = false;
}
}
async applyFilter(filter: (send: SendView) => boolean = null) {
this.filter = filter;
await this.search(null);
}
async search(timeout: number = null) {
this.searchPending = false;
if (this.searchTimeout != null) {
clearTimeout(this.searchTimeout);
}
if (timeout == null) {
this.hasSearched = this.searchService.isSearchable(this.searchText);
this.filteredSends = this.sends.filter((s) => this.filter == null || this.filter(s));
this.applyTextSearch();
return;
}
this.searchPending = true;
this.searchTimeout = setTimeout(async () => {
this.hasSearched = this.searchService.isSearchable(this.searchText);
this.filteredSends = this.sends.filter((s) => this.filter == null || this.filter(s));
this.applyTextSearch();
this.searchPending = false;
}, timeout);
}
async removePassword(s: SendView): Promise<boolean> {
if (this.actionPromise != null || s.password == null) {
return;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("removePasswordConfirmation"),
this.i18nService.t("removePassword"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
async load(filter: (send: SendView) => boolean = null) {
this.loading = true;
const sends = await this.sendService.getAllDecrypted();
this.sends = sends;
if (this.onSuccessfulLoad != null) {
await this.onSuccessfulLoad();
} else {
// Default action
this.selectAll();
}
this.loading = false;
this.loaded = true;
try {
this.actionPromise = this.sendService.removePasswordWithServer(s.id);
await this.actionPromise;
if (this.onSuccessfulRemovePassword != null) {
this.onSuccessfulRemovePassword();
} else {
// Default actions
this.platformUtilsService.showToast("success", null, this.i18nService.t("removedPassword"));
await this.load();
}
} catch (e) {
this.logService.error(e);
}
this.actionPromise = null;
}
async delete(s: SendView): Promise<boolean> {
if (this.actionPromise != null) {
return false;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("deleteSendConfirmation"),
this.i18nService.t("deleteSend"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
async reload(filter: (send: SendView) => boolean = null) {
this.loaded = false;
this.sends = [];
await this.load(filter);
try {
this.actionPromise = this.sendService.deleteWithServer(s.id);
await this.actionPromise;
if (this.onSuccessfulDelete != null) {
this.onSuccessfulDelete();
} else {
// Default actions
this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedSend"));
await this.refresh();
}
} catch (e) {
this.logService.error(e);
}
this.actionPromise = null;
return true;
}
async refresh() {
try {
this.refreshing = true;
await this.reload(this.filter);
} finally {
this.refreshing = false;
}
}
async applyFilter(filter: (send: SendView) => boolean = null) {
this.filter = filter;
await this.search(null);
}
async search(timeout: number = null) {
this.searchPending = false;
if (this.searchTimeout != null) {
clearTimeout(this.searchTimeout);
}
if (timeout == null) {
this.hasSearched = this.searchService.isSearchable(this.searchText);
this.filteredSends = this.sends.filter(s => this.filter == null || this.filter(s));
this.applyTextSearch();
return;
}
this.searchPending = true;
this.searchTimeout = setTimeout(async () => {
this.hasSearched = this.searchService.isSearchable(this.searchText);
this.filteredSends = this.sends.filter(s => this.filter == null || this.filter(s));
this.applyTextSearch();
this.searchPending = false;
}, timeout);
}
async removePassword(s: SendView): Promise<boolean> {
if (this.actionPromise != null || s.password == null) {
return;
}
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('removePasswordConfirmation'),
this.i18nService.t('removePassword'),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
try {
this.actionPromise = this.sendService.removePasswordWithServer(s.id);
await this.actionPromise;
if (this.onSuccessfulRemovePassword != null) {
this.onSuccessfulRemovePassword();
} else {
// Default actions
this.platformUtilsService.showToast('success', null, this.i18nService.t('removedPassword'));
await this.load();
}
} catch (e) {
this.logService.error(e);
}
this.actionPromise = null;
}
async delete(s: SendView): Promise<boolean> {
if (this.actionPromise != null) {
return false;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('deleteSendConfirmation'),
this.i18nService.t('deleteSend'),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
try {
this.actionPromise = this.sendService.deleteWithServer(s.id);
await this.actionPromise;
if (this.onSuccessfulDelete != null) {
this.onSuccessfulDelete();
} else {
// Default actions
this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedSend'));
await this.refresh();
}
} catch (e) {
this.logService.error(e);
}
this.actionPromise = null;
return true;
}
copy(s: SendView) {
const sendLinkBaseUrl = this.environmentService.getSendUrl();
const link = sendLinkBaseUrl + s.accessId + '/' + s.urlB64Key;
this.platformUtilsService.copyToClipboard(link);
this.platformUtilsService.showToast('success', null,
this.i18nService.t('valueCopied', this.i18nService.t('sendLink')));
}
searchTextChanged() {
this.search(200);
}
selectAll() {
this.clearSelections();
this.selectedAll = true;
this.applyFilter(null);
}
selectType(type: SendType) {
this.clearSelections();
this.selectedType = type;
this.applyFilter(s => s.type === type);
}
clearSelections() {
this.selectedAll = false;
this.selectedType = null;
}
private applyTextSearch() {
if (this.searchText != null) {
this.filteredSends = this.searchService.searchSends(this.filteredSends, this.searchText);
}
copy(s: SendView) {
const sendLinkBaseUrl = this.environmentService.getSendUrl();
const link = sendLinkBaseUrl + s.accessId + "/" + s.urlB64Key;
this.platformUtilsService.copyToClipboard(link);
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("valueCopied", this.i18nService.t("sendLink"))
);
}
searchTextChanged() {
this.search(200);
}
selectAll() {
this.clearSelections();
this.selectedAll = true;
this.applyFilter(null);
}
selectType(type: SendType) {
this.clearSelections();
this.selectedType = type;
this.applyFilter((s) => s.type === type);
}
clearSelections() {
this.selectedAll = false;
this.selectedType = null;
}
private applyTextSearch() {
if (this.searchText != null) {
this.filteredSends = this.searchService.searchSends(this.filteredSends, this.searchText);
}
}
}

View File

@@ -1,152 +1,184 @@
import { Directive } from '@angular/core';
import {
ActivatedRoute,
Router
} from '@angular/router';
import { Directive } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from 'rxjs/operators';
import { first } from "rxjs/operators";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { EncString } from 'jslib-common/models/domain/encString';
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
import { EncString } from "jslib-common/models/domain/encString";
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
import { KeysRequest } from 'jslib-common/models/request/keysRequest';
import { OrganizationUserResetPasswordEnrollmentRequest } from 'jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest';
import { SetPasswordRequest } from 'jslib-common/models/request/setPasswordRequest';
import { KeysRequest } from "jslib-common/models/request/keysRequest";
import { OrganizationUserResetPasswordEnrollmentRequest } from "jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest";
import { SetPasswordRequest } from "jslib-common/models/request/setPasswordRequest";
import { ChangePasswordComponent as BaseChangePasswordComponent } from './change-password.component';
import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component";
import { HashPurpose } from 'jslib-common/enums/hashPurpose';
import { KdfType } from 'jslib-common/enums/kdfType';
import { HashPurpose } from "jslib-common/enums/hashPurpose";
import { KdfType } from "jslib-common/enums/kdfType";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
@Directive()
export class SetPasswordComponent extends BaseChangePasswordComponent {
syncLoading: boolean = true;
showPassword: boolean = false;
hint: string = '';
identifier: string = null;
orgId: string;
resetPasswordAutoEnroll = false;
syncLoading: boolean = true;
showPassword: boolean = false;
hint: string = "";
identifier: string = null;
orgId: string;
resetPasswordAutoEnroll = false;
onSuccessfulChangePassword: () => Promise<any>;
successRoute = 'vault';
onSuccessfulChangePassword: () => Promise<any>;
successRoute = "vault";
constructor(i18nService: I18nService, cryptoService: CryptoService,
messagingService: MessagingService, passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService, policyService: PolicyService,
protected router: Router, private apiService: ApiService,
private syncService: SyncService, private route: ActivatedRoute, stateService: StateService) {
super(i18nService, cryptoService, messagingService, passwordGenerationService,
platformUtilsService, policyService, stateService);
constructor(
i18nService: I18nService,
cryptoService: CryptoService,
messagingService: MessagingService,
passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService,
policyService: PolicyService,
protected router: Router,
private apiService: ApiService,
private syncService: SyncService,
private route: ActivatedRoute,
stateService: StateService
) {
super(
i18nService,
cryptoService,
messagingService,
passwordGenerationService,
platformUtilsService,
policyService,
stateService
);
}
async ngOnInit() {
await this.syncService.fullSync(true);
this.syncLoading = false;
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
if (qParams.identifier != null) {
this.identifier = qParams.identifier;
}
});
// Automatic Enrollment Detection
if (this.identifier != null) {
try {
const response = await this.apiService.getOrganizationAutoEnrollStatus(this.identifier);
this.orgId = response.id;
this.resetPasswordAutoEnroll = response.resetPasswordEnabled;
} catch {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
}
}
async ngOnInit() {
await this.syncService.fullSync(true);
this.syncLoading = false;
super.ngOnInit();
}
this.route.queryParams.pipe(first()).subscribe(async qParams => {
if (qParams.identifier != null) {
this.identifier = qParams.identifier;
async setupSubmitActions() {
this.kdf = KdfType.PBKDF2_SHA256;
const useLowerKdf = this.platformUtilsService.isIE();
this.kdfIterations = useLowerKdf ? 10000 : 100000;
return true;
}
async performSubmitActions(
masterPasswordHash: string,
key: SymmetricCryptoKey,
encKey: [SymmetricCryptoKey, EncString]
) {
const keys = await this.cryptoService.makeKeyPair(encKey[0]);
const request = new SetPasswordRequest(
masterPasswordHash,
encKey[1].encryptedString,
this.hint,
this.kdf,
this.kdfIterations,
this.identifier,
new KeysRequest(keys[0], keys[1].encryptedString)
);
try {
if (this.resetPasswordAutoEnroll) {
this.formPromise = this.apiService
.setPassword(request)
.then(async () => {
await this.onSetPasswordSuccess(key, encKey, keys);
return this.apiService.getOrganizationKeys(this.orgId);
})
.then(async (response) => {
if (response == null) {
throw new Error(this.i18nService.t("resetPasswordOrgKeysError"));
}
const userId = await this.stateService.getUserId();
const publicKey = Utils.fromB64ToArray(response.publicKey);
// RSA Encrypt user's encKey.key with organization public key
const userEncKey = await this.cryptoService.getEncKey();
const encryptedKey = await this.cryptoService.rsaEncrypt(
userEncKey.key,
publicKey.buffer
);
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
resetRequest.resetPasswordKey = encryptedKey.encryptedString;
return this.apiService.putOrganizationUserResetPasswordEnrollment(
this.orgId,
userId,
resetRequest
);
});
} else {
this.formPromise = this.apiService.setPassword(request).then(async () => {
await this.onSetPasswordSuccess(key, encKey, keys);
});
}
// Automatic Enrollment Detection
if (this.identifier != null) {
try {
const response = await this.apiService.getOrganizationAutoEnrollStatus(this.identifier);
this.orgId = response.id;
this.resetPasswordAutoEnroll = response.resetPasswordEnabled;
} catch {
this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred'));
}
}
await this.formPromise;
super.ngOnInit();
if (this.onSuccessfulChangePassword != null) {
this.onSuccessfulChangePassword();
} else {
this.router.navigate([this.successRoute]);
}
} catch {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
}
}
async setupSubmitActions() {
this.kdf = KdfType.PBKDF2_SHA256;
const useLowerKdf = this.platformUtilsService.isIE();
this.kdfIterations = useLowerKdf ? 10000 : 100000;
return true;
}
togglePassword(confirmField: boolean) {
this.showPassword = !this.showPassword;
document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus();
}
async performSubmitActions(masterPasswordHash: string, key: SymmetricCryptoKey,
encKey: [SymmetricCryptoKey, EncString]) {
const keys = await this.cryptoService.makeKeyPair(encKey[0]);
const request = new SetPasswordRequest(
masterPasswordHash,
encKey[1].encryptedString,
this.hint,
this.kdf,
this.kdfIterations,
this.identifier,
new KeysRequest(keys[0], keys[1].encryptedString)
);
try {
if (this.resetPasswordAutoEnroll) {
this.formPromise = this.apiService.setPassword(request).then(async () => {
await this.onSetPasswordSuccess(key, encKey, keys);
return this.apiService.getOrganizationKeys(this.orgId);
}).then(async response => {
if (response == null) {
throw new Error(this.i18nService.t('resetPasswordOrgKeysError'));
}
const userId = await this.stateService.getUserId();
const publicKey = Utils.fromB64ToArray(response.publicKey);
private async onSetPasswordSuccess(
key: SymmetricCryptoKey,
encKey: [SymmetricCryptoKey, EncString],
keys: [string, EncString]
) {
await this.stateService.setKdfType(this.kdf);
await this.stateService.setKdfIterations(this.kdfIterations);
await this.cryptoService.setKey(key);
await this.cryptoService.setEncKey(encKey[1].encryptedString);
await this.cryptoService.setEncPrivateKey(keys[1].encryptedString);
// RSA Encrypt user's encKey.key with organization public key
const userEncKey = await this.cryptoService.getEncKey();
const encryptedKey = await this.cryptoService.rsaEncrypt(userEncKey.key, publicKey.buffer);
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
resetRequest.resetPasswordKey = encryptedKey.encryptedString;
return this.apiService.putOrganizationUserResetPasswordEnrollment(this.orgId, userId, resetRequest);
});
} else {
this.formPromise = this.apiService.setPassword(request).then(async () => {
await this.onSetPasswordSuccess(key, encKey, keys);
});
}
await this.formPromise;
if (this.onSuccessfulChangePassword != null) {
this.onSuccessfulChangePassword();
} else {
this.router.navigate([this.successRoute]);
}
} catch {
this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred'));
}
}
togglePassword(confirmField: boolean) {
this.showPassword = !this.showPassword;
document.getElementById(confirmField ? 'masterPasswordRetype' : 'masterPassword').focus();
}
private async onSetPasswordSuccess(key: SymmetricCryptoKey, encKey: [SymmetricCryptoKey, EncString], keys: [string, EncString]) {
await this.stateService.setKdfType(this.kdf);
await this.stateService.setKdfIterations(this.kdfIterations);
await this.cryptoService.setKey(key);
await this.cryptoService.setEncKey(encKey[1].encryptedString);
await this.cryptoService.setEncPrivateKey(keys[1].encryptedString);
const localKeyHash = await this.cryptoService.hashPassword(this.masterPassword, key,
HashPurpose.LocalAuthorization);
await this.cryptoService.setKeyHash(localKeyHash);
}
const localKeyHash = await this.cryptoService.hashPassword(
this.masterPassword,
key,
HashPurpose.LocalAuthorization
);
await this.cryptoService.setKeyHash(localKeyHash);
}
}

View File

@@ -1,53 +1,55 @@
import {
Directive,
OnInit
} from '@angular/core';
import { Directive, OnInit } from "@angular/core";
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
import { ModalRef } from './modal/modal.ref';
import { ModalRef } from "./modal/modal.ref";
@Directive()
export class SetPinComponent implements OnInit {
pin = '';
showPin = false;
masterPassOnRestart = true;
showMasterPassOnRestart = true;
pin = "";
showPin = false;
masterPassOnRestart = true;
showMasterPassOnRestart = true;
constructor(private modalRef: ModalRef, private cryptoService: CryptoService,
private keyConnectorService: KeyConnectorService, private stateService: StateService) { }
constructor(
private modalRef: ModalRef,
private cryptoService: CryptoService,
private keyConnectorService: KeyConnectorService,
private stateService: StateService
) {}
async ngOnInit() {
this.showMasterPassOnRestart = this.masterPassOnRestart = !await this.keyConnectorService.getUsesKeyConnector();
async ngOnInit() {
this.showMasterPassOnRestart = this.masterPassOnRestart =
!(await this.keyConnectorService.getUsesKeyConnector());
}
toggleVisibility() {
this.showPin = !this.showPin;
}
async submit() {
if (Utils.isNullOrWhitespace(this.pin)) {
this.modalRef.close(false);
}
toggleVisibility() {
this.showPin = !this.showPin;
const kdf = await this.stateService.getKdfType();
const kdfIterations = await this.stateService.getKdfIterations();
const email = await this.stateService.getEmail();
const pinKey = await this.cryptoService.makePinKey(this.pin, email, kdf, kdfIterations);
const key = await this.cryptoService.getKey();
const pinProtectedKey = await this.cryptoService.encrypt(key.key, pinKey);
if (this.masterPassOnRestart) {
const encPin = await this.cryptoService.encrypt(this.pin);
await this.stateService.setProtectedPin(encPin.encryptedString);
await this.stateService.setDecryptedPinProtected(pinProtectedKey);
} else {
await this.stateService.setEncryptedPinProtected(pinProtectedKey.encryptedString);
}
async submit() {
if (Utils.isNullOrWhitespace(this.pin)) {
this.modalRef.close(false);
}
const kdf = await this.stateService.getKdfType();
const kdfIterations = await this.stateService.getKdfIterations();
const email = await this.stateService.getEmail();
const pinKey = await this.cryptoService.makePinKey(this.pin, email, kdf, kdfIterations);
const key = await this.cryptoService.getKey();
const pinProtectedKey = await this.cryptoService.encrypt(key.key, pinKey);
if (this.masterPassOnRestart) {
const encPin = await this.cryptoService.encrypt(this.pin);
await this.stateService.setProtectedPin(encPin.encryptedString);
await this.stateService.setDecryptedPinProtected(pinProtectedKey);
} else {
await this.stateService.setEncryptedPinProtected(pinProtectedKey.encryptedString);
}
this.modalRef.close(true);
}
this.modalRef.close(true);
}
}

View File

@@ -1,138 +1,140 @@
import { Directive, Input, OnInit } from "@angular/core";
import {
Directive,
Input,
OnInit,
} from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
FormBuilder,
ValidationErrors,
Validator
} from '@angular/forms';
AbstractControl,
ControlValueAccessor,
FormBuilder,
ValidationErrors,
Validator,
} from "@angular/forms";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { PolicyType } from 'jslib-common/enums/policyType';
import { Policy } from 'jslib-common/models/domain/policy';
import { PolicyType } from "jslib-common/enums/policyType";
import { Policy } from "jslib-common/models/domain/policy";
@Directive()
export class VaultTimeoutInputComponent implements ControlValueAccessor, Validator, OnInit {
get showCustom() {
return this.form.get("vaultTimeout").value === VaultTimeoutInputComponent.CUSTOM_VALUE;
}
get showCustom() {
return this.form.get('vaultTimeout').value === VaultTimeoutInputComponent.CUSTOM_VALUE;
static CUSTOM_VALUE = -100;
form = this.fb.group({
vaultTimeout: [null],
custom: this.fb.group({
hours: [null],
minutes: [null],
}),
});
@Input() vaultTimeouts: { name: string; value: number }[];
vaultTimeoutPolicy: Policy;
vaultTimeoutPolicyHours: number;
vaultTimeoutPolicyMinutes: number;
private onChange: (vaultTimeout: number) => void;
private validatorChange: () => void;
constructor(
private fb: FormBuilder,
private policyService: PolicyService,
private i18nService: I18nService
) {}
async ngOnInit() {
if (await this.policyService.policyAppliesToUser(PolicyType.MaximumVaultTimeout)) {
const vaultTimeoutPolicy = await this.policyService.getAll(PolicyType.MaximumVaultTimeout);
this.vaultTimeoutPolicy = vaultTimeoutPolicy[0];
this.vaultTimeoutPolicyHours = Math.floor(this.vaultTimeoutPolicy.data.minutes / 60);
this.vaultTimeoutPolicyMinutes = this.vaultTimeoutPolicy.data.minutes % 60;
this.vaultTimeouts = this.vaultTimeouts.filter(
(t) =>
t.value <= this.vaultTimeoutPolicy.data.minutes &&
(t.value > 0 || t.value === VaultTimeoutInputComponent.CUSTOM_VALUE) &&
t.value != null
);
this.validatorChange();
}
static CUSTOM_VALUE = -100;
form = this.fb.group({
vaultTimeout: [null],
custom: this.fb.group({
hours: [null],
minutes: [null],
}),
this.form.valueChanges.subscribe(async (value) => {
this.onChange(this.getVaultTimeout(value));
});
@Input() vaultTimeouts: { name: string; value: number; }[];
vaultTimeoutPolicy: Policy;
vaultTimeoutPolicyHours: number;
vaultTimeoutPolicyMinutes: number;
// Assign the previous value to the custom fields
this.form.get("vaultTimeout").valueChanges.subscribe((value) => {
if (value !== VaultTimeoutInputComponent.CUSTOM_VALUE) {
return;
}
private onChange: (vaultTimeout: number) => void;
private validatorChange: () => void;
const current = Math.max(this.form.value.vaultTimeout, 0);
this.form.patchValue({
custom: {
hours: Math.floor(current / 60),
minutes: current % 60,
},
});
});
}
constructor(private fb: FormBuilder, private policyService: PolicyService, private i18nService: I18nService) {
ngOnChanges() {
this.vaultTimeouts.push({
name: this.i18nService.t("custom"),
value: VaultTimeoutInputComponent.CUSTOM_VALUE,
});
}
getVaultTimeout(value: any) {
if (value.vaultTimeout !== VaultTimeoutInputComponent.CUSTOM_VALUE) {
return value.vaultTimeout;
}
async ngOnInit() {
if (await this.policyService.policyAppliesToUser(PolicyType.MaximumVaultTimeout)) {
const vaultTimeoutPolicy = await this.policyService.getAll(PolicyType.MaximumVaultTimeout);
return value.custom.hours * 60 + value.custom.minutes;
}
this.vaultTimeoutPolicy = vaultTimeoutPolicy[0];
this.vaultTimeoutPolicyHours = Math.floor(this.vaultTimeoutPolicy.data.minutes / 60);
this.vaultTimeoutPolicyMinutes = this.vaultTimeoutPolicy.data.minutes % 60;
this.vaultTimeouts = this.vaultTimeouts.filter(t =>
t.value <= this.vaultTimeoutPolicy.data.minutes &&
(t.value > 0 || t.value === VaultTimeoutInputComponent.CUSTOM_VALUE) &&
t.value != null
);
this.validatorChange();
}
this.form.valueChanges.subscribe(async value => {
this.onChange(this.getVaultTimeout(value));
});
// Assign the previous value to the custom fields
this.form.get('vaultTimeout').valueChanges.subscribe(value => {
if (value !== VaultTimeoutInputComponent.CUSTOM_VALUE) {
return;
}
const current = Math.max(this.form.value.vaultTimeout, 0);
this.form.patchValue({
custom: {
hours: Math.floor(current / 60),
minutes: current % 60,
},
});
});
writeValue(value: number): void {
if (value == null) {
return;
}
ngOnChanges() {
this.vaultTimeouts.push({ name: this.i18nService.t('custom'), value: VaultTimeoutInputComponent.CUSTOM_VALUE });
if (this.vaultTimeouts.every((p) => p.value !== value)) {
this.form.setValue({
vaultTimeout: VaultTimeoutInputComponent.CUSTOM_VALUE,
custom: {
hours: Math.floor(value / 60),
minutes: value % 60,
},
});
return;
}
getVaultTimeout(value: any) {
if (value.vaultTimeout !== VaultTimeoutInputComponent.CUSTOM_VALUE) {
return value.vaultTimeout;
}
this.form.patchValue({
vaultTimeout: value,
});
}
return value.custom.hours * 60 + value.custom.minutes;
registerOnChange(onChange: any): void {
this.onChange = onChange;
}
// tslint:disable-next-line
registerOnTouched(onTouched: any): void {}
// tslint:disable-next-line
setDisabledState?(isDisabled: boolean): void {}
validate(control: AbstractControl): ValidationErrors {
if (this.vaultTimeoutPolicy && this.vaultTimeoutPolicy?.data?.minutes < control.value) {
return { policyError: true };
}
writeValue(value: number): void {
if (value == null) {
return;
}
return null;
}
if (this.vaultTimeouts.every(p => p.value !== value)) {
this.form.setValue({
vaultTimeout: VaultTimeoutInputComponent.CUSTOM_VALUE,
custom: {
hours: Math.floor(value / 60),
minutes: value % 60,
},
});
return;
}
this.form.patchValue({
vaultTimeout: value,
});
}
registerOnChange(onChange: any): void {
this.onChange = onChange;
}
// tslint:disable-next-line
registerOnTouched(onTouched: any): void {}
// tslint:disable-next-line
setDisabledState?(isDisabled: boolean): void { }
validate(control: AbstractControl): ValidationErrors {
if (this.vaultTimeoutPolicy && this.vaultTimeoutPolicy?.data?.minutes < control.value) {
return { policyError: true };
}
return null;
}
registerOnValidatorChange(fn: () => void): void {
this.validatorChange = fn;
}
registerOnValidatorChange(fn: () => void): void {
this.validatorChange = fn;
}
}

View File

@@ -1,108 +1,119 @@
import {
Directive,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { OrganizationUserStatusType } from 'jslib-common/enums/organizationUserStatusType';
import { OrganizationUserStatusType } from "jslib-common/enums/organizationUserStatusType";
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { Organization } from 'jslib-common/models/domain/organization';
import { CipherView } from 'jslib-common/models/view/cipherView';
import { CollectionView } from 'jslib-common/models/view/collectionView';
import { Organization } from "jslib-common/models/domain/organization";
import { CipherView } from "jslib-common/models/view/cipherView";
import { CollectionView } from "jslib-common/models/view/collectionView";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
@Directive()
export class ShareComponent implements OnInit {
@Input() cipherId: string;
@Input() organizationId: string;
@Output() onSharedCipher = new EventEmitter();
@Input() cipherId: string;
@Input() organizationId: string;
@Output() onSharedCipher = new EventEmitter();
formPromise: Promise<any>;
cipher: CipherView;
collections: CollectionView[] = [];
organizations: Organization[] = [];
formPromise: Promise<any>;
cipher: CipherView;
collections: CollectionView[] = [];
organizations: Organization[] = [];
protected writeableCollections: CollectionView[] = [];
protected writeableCollections: CollectionView[] = [];
constructor(protected collectionService: CollectionService, protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService, protected cipherService: CipherService,
private logService: LogService, protected organizationService: OrganizationService) { }
constructor(
protected collectionService: CollectionService,
protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService,
protected cipherService: CipherService,
private logService: LogService,
protected organizationService: OrganizationService
) {}
async ngOnInit() {
await this.load();
async ngOnInit() {
await this.load();
}
async load() {
const allCollections = await this.collectionService.getAllDecrypted();
this.writeableCollections = allCollections.map((c) => c).filter((c) => !c.readOnly);
const orgs = await this.organizationService.getAll();
this.organizations = orgs
.sort(Utils.getSortFunction(this.i18nService, "name"))
.filter((o) => o.enabled && o.status === OrganizationUserStatusType.Confirmed);
const cipherDomain = await this.cipherService.get(this.cipherId);
this.cipher = await cipherDomain.decrypt();
if (this.organizationId == null && this.organizations.length > 0) {
this.organizationId = this.organizations[0].id;
}
this.filterCollections();
}
filterCollections() {
this.writeableCollections.forEach((c) => ((c as any).checked = false));
if (this.organizationId == null || this.writeableCollections.length === 0) {
this.collections = [];
} else {
this.collections = this.writeableCollections.filter(
(c) => c.organizationId === this.organizationId
);
}
}
async submit(): Promise<boolean> {
const selectedCollectionIds = this.collections
.filter((c) => !!(c as any).checked)
.map((c) => c.id);
if (selectedCollectionIds.length === 0) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("selectOneCollection")
);
return;
}
async load() {
const allCollections = await this.collectionService.getAllDecrypted();
this.writeableCollections = allCollections.map(c => c).filter(c => !c.readOnly);
const orgs = await this.organizationService.getAll();
this.organizations = orgs.sort(Utils.getSortFunction(this.i18nService, 'name'))
.filter(o => o.enabled && o.status === OrganizationUserStatusType.Confirmed);
const cipherDomain = await this.cipherService.get(this.cipherId);
const cipherView = await cipherDomain.decrypt();
const orgName =
this.organizations.find((o) => o.id === this.organizationId)?.name ??
this.i18nService.t("organization");
const cipherDomain = await this.cipherService.get(this.cipherId);
this.cipher = await cipherDomain.decrypt();
if (this.organizationId == null && this.organizations.length > 0) {
this.organizationId = this.organizations[0].id;
}
this.filterCollections();
try {
this.formPromise = this.cipherService
.shareWithServer(cipherView, this.organizationId, selectedCollectionIds)
.then(async () => {
this.onSharedCipher.emit();
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("movedItemToOrg", cipherView.name, orgName)
);
});
await this.formPromise;
return true;
} catch (e) {
this.logService.error(e);
}
return false;
}
filterCollections() {
this.writeableCollections.forEach(c => (c as any).checked = false);
if (this.organizationId == null || this.writeableCollections.length === 0) {
this.collections = [];
} else {
this.collections = this.writeableCollections.filter(c => c.organizationId === this.organizationId);
get canSave() {
if (this.collections != null) {
for (let i = 0; i < this.collections.length; i++) {
if ((this.collections[i] as any).checked) {
return true;
}
}
}
async submit(): Promise<boolean> {
const selectedCollectionIds = this.collections
.filter(c => !!(c as any).checked)
.map(c => c.id);
if (selectedCollectionIds.length === 0) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('selectOneCollection'));
return;
}
const cipherDomain = await this.cipherService.get(this.cipherId);
const cipherView = await cipherDomain.decrypt();
const orgName = this.organizations.find(o => o.id === this.organizationId)?.name ?? this.i18nService.t('organization');
try {
this.formPromise = this.cipherService.shareWithServer(cipherView, this.organizationId,
selectedCollectionIds).then(async () => {
this.onSharedCipher.emit();
this.platformUtilsService.showToast('success', null,
this.i18nService.t('movedItemToOrg', cipherView.name, orgName));
});
await this.formPromise;
return true;
} catch (e) {
this.logService.error(e);
}
return false;
}
get canSave() {
if (this.collections != null) {
for (let i = 0; i < this.collections.length; i++) {
if ((this.collections[i] as any).checked) {
return true;
}
}
}
return false;
}
return false;
}
}

View File

@@ -1,211 +1,254 @@
import { Directive } from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { Directive } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from 'rxjs/operators';
import { first } from "rxjs/operators";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
import { AuthResult } from 'jslib-common/models/domain/authResult';
import { AuthResult } from "jslib-common/models/domain/authResult";
@Directive()
export class SsoComponent {
identifier: string;
loggingIn = false;
identifier: string;
loggingIn = false;
formPromise: Promise<AuthResult>;
initiateSsoFormPromise: Promise<any>;
onSuccessfulLogin: () => Promise<any>;
onSuccessfulLoginNavigate: () => Promise<any>;
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>;
onSuccessfulLoginChangePasswordNavigate: () => Promise<any>;
onSuccessfulLoginForceResetNavigate: () => Promise<any>;
formPromise: Promise<AuthResult>;
initiateSsoFormPromise: Promise<any>;
onSuccessfulLogin: () => Promise<any>;
onSuccessfulLoginNavigate: () => Promise<any>;
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>;
onSuccessfulLoginChangePasswordNavigate: () => Promise<any>;
onSuccessfulLoginForceResetNavigate: () => Promise<any>;
protected twoFactorRoute = '2fa';
protected successRoute = 'lock';
protected changePasswordRoute = 'set-password';
protected forcePasswordResetRoute = 'update-temp-password';
protected clientId: string;
protected redirectUri: string;
protected state: string;
protected codeChallenge: string;
protected twoFactorRoute = "2fa";
protected successRoute = "lock";
protected changePasswordRoute = "set-password";
protected forcePasswordResetRoute = "update-temp-password";
protected clientId: string;
protected redirectUri: string;
protected state: string;
protected codeChallenge: string;
constructor(protected authService: AuthService, protected router: Router,
protected i18nService: I18nService, protected route: ActivatedRoute,
protected stateService: StateService, protected platformUtilsService: PlatformUtilsService,
protected apiService: ApiService, protected cryptoFunctionService: CryptoFunctionService,
protected environmentService: EnvironmentService, protected passwordGenerationService: PasswordGenerationService,
protected logService: LogService) { }
constructor(
protected authService: AuthService,
protected router: Router,
protected i18nService: I18nService,
protected route: ActivatedRoute,
protected stateService: StateService,
protected platformUtilsService: PlatformUtilsService,
protected apiService: ApiService,
protected cryptoFunctionService: CryptoFunctionService,
protected environmentService: EnvironmentService,
protected passwordGenerationService: PasswordGenerationService,
protected logService: LogService
) {}
async ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async qParams => {
if (qParams.code != null && qParams.state != null) {
const codeVerifier = await this.stateService.getSsoCodeVerifier();
const state = await this.stateService.getSsoState();
await this.stateService.setSsoCodeVerifier(null);
await this.stateService.setSsoState(null);
if (qParams.code != null && codeVerifier != null && state != null && this.checkState(state, qParams.state)) {
await this.logIn(qParams.code, codeVerifier, this.getOrgIdentifierFromState(qParams.state));
}
} else if (qParams.clientId != null && qParams.redirectUri != null && qParams.state != null &&
qParams.codeChallenge != null) {
this.redirectUri = qParams.redirectUri;
this.state = qParams.state;
this.codeChallenge = qParams.codeChallenge;
this.clientId = qParams.clientId;
}
});
async ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
if (qParams.code != null && qParams.state != null) {
const codeVerifier = await this.stateService.getSsoCodeVerifier();
const state = await this.stateService.getSsoState();
await this.stateService.setSsoCodeVerifier(null);
await this.stateService.setSsoState(null);
if (
qParams.code != null &&
codeVerifier != null &&
state != null &&
this.checkState(state, qParams.state)
) {
await this.logIn(
qParams.code,
codeVerifier,
this.getOrgIdentifierFromState(qParams.state)
);
}
} else if (
qParams.clientId != null &&
qParams.redirectUri != null &&
qParams.state != null &&
qParams.codeChallenge != null
) {
this.redirectUri = qParams.redirectUri;
this.state = qParams.state;
this.codeChallenge = qParams.codeChallenge;
this.clientId = qParams.clientId;
}
});
}
async submit(returnUri?: string, includeUserIdentifier?: boolean) {
this.initiateSsoFormPromise = this.preValidate();
if (await this.initiateSsoFormPromise) {
const authorizeUrl = await this.buildAuthorizeUrl(returnUri, includeUserIdentifier);
this.platformUtilsService.launchUri(authorizeUrl, { sameWindow: true });
}
}
async preValidate(): Promise<boolean> {
if (this.identifier == null || this.identifier === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("ssoValidationFailed"),
this.i18nService.t("ssoIdentifierRequired")
);
return false;
}
return await this.apiService.preValidateSso(this.identifier);
}
protected async buildAuthorizeUrl(
returnUri?: string,
includeUserIdentifier?: boolean
): Promise<string> {
let codeChallenge = this.codeChallenge;
let state = this.state;
const passwordOptions: any = {
type: "password",
length: 64,
uppercase: true,
lowercase: true,
numbers: true,
special: false,
};
if (codeChallenge == null) {
const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, "sha256");
codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
await this.stateService.setSsoCodeVerifier(codeVerifier);
}
async submit(returnUri?: string, includeUserIdentifier?: boolean) {
this.initiateSsoFormPromise = this.preValidate();
if (await this.initiateSsoFormPromise) {
const authorizeUrl = await this.buildAuthorizeUrl(returnUri, includeUserIdentifier);
this.platformUtilsService.launchUri(authorizeUrl, { sameWindow: true });
}
if (state == null) {
state = await this.passwordGenerationService.generatePassword(passwordOptions);
if (returnUri) {
state += `_returnUri='${returnUri}'`;
}
}
async preValidate(): Promise<boolean> {
if (this.identifier == null || this.identifier === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('ssoValidationFailed'),
this.i18nService.t('ssoIdentifierRequired'));
return false;
}
return await this.apiService.preValidateSso(this.identifier);
// Add Organization Identifier to state
state += `_identifier=${this.identifier}`;
// Save state (regardless of new or existing)
await this.stateService.setSsoState(state);
let authorizeUrl =
this.environmentService.getIdentityUrl() +
"/connect/authorize?" +
"client_id=" +
this.clientId +
"&redirect_uri=" +
encodeURIComponent(this.redirectUri) +
"&" +
"response_type=code&scope=api offline_access&" +
"state=" +
state +
"&code_challenge=" +
codeChallenge +
"&" +
"code_challenge_method=S256&response_mode=query&" +
"domain_hint=" +
encodeURIComponent(this.identifier);
if (includeUserIdentifier) {
const userIdentifier = await this.apiService.getSsoUserIdentifier();
authorizeUrl += `&user_identifier=${encodeURIComponent(userIdentifier)}`;
}
protected async buildAuthorizeUrl(returnUri?: string, includeUserIdentifier?: boolean): Promise<string> {
let codeChallenge = this.codeChallenge;
let state = this.state;
return authorizeUrl;
}
const passwordOptions: any = {
type: 'password',
length: 64,
uppercase: true,
lowercase: true,
numbers: true,
special: false,
};
if (codeChallenge == null) {
const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, 'sha256');
codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
await this.stateService.setSsoCodeVerifier(codeVerifier);
private async logIn(code: string, codeVerifier: string, orgIdFromState: string) {
this.loggingIn = true;
try {
this.formPromise = this.authService.logInSso(
code,
codeVerifier,
this.redirectUri,
orgIdFromState
);
const response = await this.formPromise;
if (response.twoFactor) {
if (this.onSuccessfulLoginTwoFactorNavigate != null) {
this.onSuccessfulLoginTwoFactorNavigate();
} else {
this.router.navigate([this.twoFactorRoute], {
queryParams: {
identifier: orgIdFromState,
sso: "true",
},
});
}
if (state == null) {
state = await this.passwordGenerationService.generatePassword(passwordOptions);
if (returnUri) {
state += `_returnUri='${returnUri}'`;
}
} else if (response.resetMasterPassword) {
if (this.onSuccessfulLoginChangePasswordNavigate != null) {
this.onSuccessfulLoginChangePasswordNavigate();
} else {
this.router.navigate([this.changePasswordRoute], {
queryParams: {
identifier: orgIdFromState,
},
});
}
// Add Organization Identifier to state
state += `_identifier=${this.identifier}`;
// Save state (regardless of new or existing)
await this.stateService.setSsoState(state);
let authorizeUrl = this.environmentService.getIdentityUrl() + '/connect/authorize?' +
'client_id=' + this.clientId + '&redirect_uri=' + encodeURIComponent(this.redirectUri) + '&' +
'response_type=code&scope=api offline_access&' +
'state=' + state + '&code_challenge=' + codeChallenge + '&' +
'code_challenge_method=S256&response_mode=query&' +
'domain_hint=' + encodeURIComponent(this.identifier);
if (includeUserIdentifier) {
const userIdentifier = await this.apiService.getSsoUserIdentifier();
authorizeUrl += `&user_identifier=${encodeURIComponent(userIdentifier)}`;
} else if (response.forcePasswordReset) {
if (this.onSuccessfulLoginForceResetNavigate != null) {
this.onSuccessfulLoginForceResetNavigate();
} else {
this.router.navigate([this.forcePasswordResetRoute]);
}
} else {
const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.setDisableFavicon(!!disableFavicon);
if (this.onSuccessfulLogin != null) {
this.onSuccessfulLogin();
}
if (this.onSuccessfulLoginNavigate != null) {
this.onSuccessfulLoginNavigate();
} else {
this.router.navigate([this.successRoute]);
}
}
} catch (e) {
this.logService.error(e);
if (e.message === "Unable to reach key connector") {
this.platformUtilsService.showToast(
"error",
null,
this.i18nService.t("ssoKeyConnectorUnavailable")
);
}
}
this.loggingIn = false;
}
return authorizeUrl;
private getOrgIdentifierFromState(state: string): string {
if (state === null || state === undefined) {
return null;
}
private async logIn(code: string, codeVerifier: string, orgIdFromState: string) {
this.loggingIn = true;
try {
this.formPromise = this.authService.logInSso(code, codeVerifier, this.redirectUri, orgIdFromState);
const response = await this.formPromise;
if (response.twoFactor) {
if (this.onSuccessfulLoginTwoFactorNavigate != null) {
this.onSuccessfulLoginTwoFactorNavigate();
} else {
this.router.navigate([this.twoFactorRoute], {
queryParams: {
identifier: orgIdFromState,
sso: 'true',
},
});
}
} else if (response.resetMasterPassword) {
if (this.onSuccessfulLoginChangePasswordNavigate != null) {
this.onSuccessfulLoginChangePasswordNavigate();
} else {
this.router.navigate([this.changePasswordRoute], {
queryParams: {
identifier: orgIdFromState,
},
});
}
} else if (response.forcePasswordReset) {
if (this.onSuccessfulLoginForceResetNavigate != null) {
this.onSuccessfulLoginForceResetNavigate();
} else {
this.router.navigate([this.forcePasswordResetRoute]);
}
} else {
const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.setDisableFavicon(!!disableFavicon);
if (this.onSuccessfulLogin != null) {
this.onSuccessfulLogin();
}
if (this.onSuccessfulLoginNavigate != null) {
this.onSuccessfulLoginNavigate();
} else {
this.router.navigate([this.successRoute]);
}
}
} catch (e) {
this.logService.error(e);
if (e.message === 'Unable to reach key connector') {
this.platformUtilsService.showToast('error', null, this.i18nService.t('ssoKeyConnectorUnavailable'));
}
}
this.loggingIn = false;
const stateSplit = state.split("_identifier=");
return stateSplit.length > 1 ? stateSplit[1] : null;
}
private checkState(state: string, checkState: string): boolean {
if (state === null || state === undefined) {
return false;
}
if (checkState === null || checkState === undefined) {
return false;
}
private getOrgIdentifierFromState(state: string): string {
if (state === null || state === undefined) {
return null;
}
const stateSplit = state.split('_identifier=');
return stateSplit.length > 1 ? stateSplit[1] : null;
}
private checkState(state: string, checkState: string): boolean {
if (state === null || state === undefined) {
return false;
}
if (checkState === null || checkState === undefined) {
return false;
}
const stateSplit = state.split('_identifier=');
const checkStateSplit = checkState.split('_identifier=');
return stateSplit[0] === checkStateSplit[0];
}
const stateSplit = state.split("_identifier=");
const checkStateSplit = checkState.split("_identifier=");
return stateSplit[0] === checkStateSplit[0];
}
}

View File

@@ -1,85 +1,95 @@
import { animate, state, style, transition, trigger } from "@angular/animations";
import { CommonModule } from "@angular/common";
import { Component, ModuleWithProviders, NgModule } from "@angular/core";
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 as BaseToast, ToastPackage, ToastrService, TOAST_CONFIG } from 'ngx-toastr';
DefaultNoComponentGlobalConfig,
GlobalConfig,
Toast as BaseToast,
ToastPackage,
ToastrService,
TOAST_CONFIG,
} from "ngx-toastr";
@Component({
selector: '[toast-component2]',
template: `
<button *ngIf="options.closeButton" (click)="remove()" type="button" class="toast-close-button" aria-label="Close">
<span aria-hidden="true">&times;</span>
selector: "[toast-component2]",
template: `
<button
*ngIf="options.closeButton"
(click)="remove()"
type="button"
class="toast-close-button"
aria-label="Close"
>
<span aria-hidden="true">&times;</span>
</button>
<div class="icon">
<i></i>
<i></i>
</div>
<div>
<div *ngIf="title" [class]="options.titleClass" [attr.aria-label]="title">
{{ title }} <ng-container *ngIf="duplicatesCount">[{{ duplicatesCount + 1 }}]</ng-container>
</div>
<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 *ngIf="title" [class]="options.titleClass" [attr.aria-label]="title">
{{ title }} <ng-container *ngIf="duplicatesCount">[{{ duplicatesCount + 1 }}]</ng-container>
</div>
<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 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,
`,
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,
})
export class BitwardenToast extends BaseToast {
constructor(protected toastrService: ToastrService, public toastPackage: ToastPackage) {
super(toastrService, toastPackage);
}
constructor(protected toastrService: ToastrService, public toastPackage: ToastPackage) {
super(toastrService, toastPackage);
}
}
export const BitwardenToastGlobalConfig: GlobalConfig = {
...DefaultNoComponentGlobalConfig,
toastComponent: BitwardenToast,
};
...DefaultNoComponentGlobalConfig,
toastComponent: BitwardenToast,
};
@NgModule({
imports: [CommonModule],
declarations: [BitwardenToast],
exports: [BitwardenToast],
imports: [CommonModule],
declarations: [BitwardenToast],
exports: [BitwardenToast],
})
export class BitwardenToastModule {
static forRoot(config: Partial<GlobalConfig> = {}): ModuleWithProviders<BitwardenToastModule> {
return {
ngModule: BitwardenToastModule,
providers: [
{
provide: TOAST_CONFIG,
useValue: {
default: BitwardenToastGlobalConfig,
config: config,
},
},
],
};
}
static forRoot(config: Partial<GlobalConfig> = {}): ModuleWithProviders<BitwardenToastModule> {
return {
ngModule: BitwardenToastModule,
providers: [
{
provide: TOAST_CONFIG,
useValue: {
default: BitwardenToastGlobalConfig,
config: config,
},
},
],
};
}
}

View File

@@ -1,38 +1,37 @@
import {
Directive,
EventEmitter,
OnInit,
Output,
} from '@angular/core';
import { Router } from '@angular/router';
import { Directive, EventEmitter, OnInit, Output } from "@angular/core";
import { Router } from "@angular/router";
import { TwoFactorProviderType } from 'jslib-common/enums/twoFactorProviderType';
import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { AuthService } from "jslib-common/abstractions/auth.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
@Directive()
export class TwoFactorOptionsComponent implements OnInit {
@Output() onProviderSelected = new EventEmitter<TwoFactorProviderType>();
@Output() onRecoverSelected = new EventEmitter();
@Output() onProviderSelected = new EventEmitter<TwoFactorProviderType>();
@Output() onRecoverSelected = new EventEmitter();
providers: any[] = [];
providers: any[] = [];
constructor(protected authService: AuthService, protected router: Router,
protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService,
protected win: Window) { }
constructor(
protected authService: AuthService,
protected router: Router,
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected win: Window
) {}
ngOnInit() {
this.providers = this.authService.getSupportedTwoFactorProviders(this.win);
}
ngOnInit() {
this.providers = this.authService.getSupportedTwoFactorProviders(this.win);
}
choose(p: any) {
this.onProviderSelected.emit(p.type);
}
choose(p: any) {
this.onProviderSelected.emit(p.type);
}
recover() {
this.platformUtilsService.launchUri('https://help.bitwarden.com/article/lost-two-step-device/');
this.onRecoverSelected.emit();
}
recover() {
this.platformUtilsService.launchUri("https://help.bitwarden.com/article/lost-two-step-device/");
this.onRecoverSelected.emit();
}
}

View File

@@ -1,251 +1,280 @@
import { Directive, OnDestroy, OnInit } from '@angular/core';
import { Directive, OnDestroy, OnInit } from "@angular/core";
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ActivatedRoute, Router } from "@angular/router";
import { first } from 'rxjs/operators';
import { first } from "rxjs/operators";
import { TwoFactorProviderType } from 'jslib-common/enums/twoFactorProviderType';
import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
import { TwoFactorEmailRequest } from 'jslib-common/models/request/twoFactorEmailRequest';
import { TwoFactorEmailRequest } from "jslib-common/models/request/twoFactorEmailRequest";
import { AuthResult } from 'jslib-common/models/domain/authResult';
import { AuthResult } from "jslib-common/models/domain/authResult";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { TwoFactorProviders } from 'jslib-common/services/auth.service';
import { TwoFactorProviders } from "jslib-common/services/auth.service";
import * as DuoWebSDK from 'duo_web_sdk';
import { WebAuthnIFrame } from 'jslib-common/misc/webauthn_iframe';
import * as DuoWebSDK from "duo_web_sdk";
import { WebAuthnIFrame } from "jslib-common/misc/webauthn_iframe";
@Directive()
export class TwoFactorComponent implements OnInit, OnDestroy {
token: string = '';
remember: boolean = false;
webAuthnReady: boolean = false;
webAuthnNewTab: boolean = false;
providers = TwoFactorProviders;
providerType = TwoFactorProviderType;
selectedProviderType: TwoFactorProviderType = TwoFactorProviderType.Authenticator;
webAuthnSupported: boolean = false;
webAuthn: WebAuthnIFrame = null;
title: string = '';
twoFactorEmail: string = null;
formPromise: Promise<any>;
emailPromise: Promise<any>;
identifier: string = null;
onSuccessfulLogin: () => Promise<any>;
onSuccessfulLoginNavigate: () => Promise<any>;
token: string = "";
remember: boolean = false;
webAuthnReady: boolean = false;
webAuthnNewTab: boolean = false;
providers = TwoFactorProviders;
providerType = TwoFactorProviderType;
selectedProviderType: TwoFactorProviderType = TwoFactorProviderType.Authenticator;
webAuthnSupported: boolean = false;
webAuthn: WebAuthnIFrame = null;
title: string = "";
twoFactorEmail: string = null;
formPromise: Promise<any>;
emailPromise: Promise<any>;
identifier: string = null;
onSuccessfulLogin: () => Promise<any>;
onSuccessfulLoginNavigate: () => Promise<any>;
get webAuthnAllow(): string {
return `publickey-credentials-get ${this.environmentService.getWebVaultUrl()}`;
get webAuthnAllow(): string {
return `publickey-credentials-get ${this.environmentService.getWebVaultUrl()}`;
}
protected loginRoute = "login";
protected successRoute = "vault";
constructor(
protected authService: AuthService,
protected router: Router,
protected i18nService: I18nService,
protected apiService: ApiService,
protected platformUtilsService: PlatformUtilsService,
protected win: Window,
protected environmentService: EnvironmentService,
protected stateService: StateService,
protected route: ActivatedRoute,
protected logService: LogService
) {
this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win);
}
async ngOnInit() {
if (!this.authing || this.authService.twoFactorProvidersData == null) {
this.router.navigate([this.loginRoute]);
return;
}
protected loginRoute = 'login';
protected successRoute = 'vault';
this.route.queryParams.pipe(first()).subscribe((qParams) => {
if (qParams.identifier != null) {
this.identifier = qParams.identifier;
}
});
constructor(protected authService: AuthService, protected router: Router,
protected i18nService: I18nService, protected apiService: ApiService,
protected platformUtilsService: PlatformUtilsService, protected win: Window,
protected environmentService: EnvironmentService, protected stateService: StateService,
protected route: ActivatedRoute, protected logService: LogService) {
this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win);
if (this.needsLock) {
this.successRoute = "lock";
}
async ngOnInit() {
if (!this.authing || this.authService.twoFactorProvidersData == null) {
this.router.navigate([this.loginRoute]);
return;
if (this.win != null && this.webAuthnSupported) {
const webVaultUrl = this.environmentService.getWebVaultUrl();
this.webAuthn = new WebAuthnIFrame(
this.win,
webVaultUrl,
this.webAuthnNewTab,
this.platformUtilsService,
this.i18nService,
(token: string) => {
this.token = token;
this.submit();
},
(error: string) => {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), error);
},
(info: string) => {
if (info === "ready") {
this.webAuthnReady = true;
}
}
this.route.queryParams.pipe(first()).subscribe(qParams => {
if (qParams.identifier != null) {
this.identifier = qParams.identifier;
}
});
if (this.needsLock) {
this.successRoute = 'lock';
}
if (this.win != null && this.webAuthnSupported) {
const webVaultUrl = this.environmentService.getWebVaultUrl();
this.webAuthn = new WebAuthnIFrame(this.win, webVaultUrl, this.webAuthnNewTab, this.platformUtilsService,
this.i18nService, (token: string) => {
this.token = token;
this.submit();
}, (error: string) => {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), error);
}, (info: string) => {
if (info === 'ready') {
this.webAuthnReady = true;
}
}
);
}
this.selectedProviderType = this.authService.getDefaultTwoFactorProvider(this.webAuthnSupported);
await this.init();
);
}
ngOnDestroy(): void {
this.cleanupWebAuthn();
this.webAuthn = null;
this.selectedProviderType = this.authService.getDefaultTwoFactorProvider(
this.webAuthnSupported
);
await this.init();
}
ngOnDestroy(): void {
this.cleanupWebAuthn();
this.webAuthn = null;
}
async init() {
if (this.selectedProviderType == null) {
this.title = this.i18nService.t("loginUnavailable");
return;
}
async init() {
if (this.selectedProviderType == null) {
this.title = this.i18nService.t('loginUnavailable');
return;
this.cleanupWebAuthn();
this.title = (TwoFactorProviders as any)[this.selectedProviderType].name;
const providerData = this.authService.twoFactorProvidersData.get(this.selectedProviderType);
switch (this.selectedProviderType) {
case TwoFactorProviderType.WebAuthn:
if (!this.webAuthnNewTab) {
setTimeout(() => {
this.authWebAuthn();
}, 500);
}
break;
case TwoFactorProviderType.Duo:
case TwoFactorProviderType.OrganizationDuo:
setTimeout(() => {
DuoWebSDK.init({
iframe: undefined,
host: providerData.Host,
sig_request: providerData.Signature,
submit_callback: async (f: HTMLFormElement) => {
const sig = f.querySelector('input[name="sig_response"]') as HTMLInputElement;
if (sig != null) {
this.token = sig.value;
await this.submit();
}
},
});
}, 0);
break;
case TwoFactorProviderType.Email:
this.twoFactorEmail = providerData.Email;
if (this.authService.twoFactorProvidersData.size > 1) {
await this.sendEmail(false);
}
break;
default:
break;
}
}
this.cleanupWebAuthn();
this.title = (TwoFactorProviders as any)[this.selectedProviderType].name;
const providerData = this.authService.twoFactorProvidersData.get(this.selectedProviderType);
switch (this.selectedProviderType) {
case TwoFactorProviderType.WebAuthn:
if (!this.webAuthnNewTab) {
setTimeout(() => {
this.authWebAuthn();
}, 500);
}
break;
case TwoFactorProviderType.Duo:
case TwoFactorProviderType.OrganizationDuo:
setTimeout(() => {
DuoWebSDK.init({
iframe: undefined,
host: providerData.Host,
sig_request: providerData.Signature,
submit_callback: async (f: HTMLFormElement) => {
const sig = f.querySelector('input[name="sig_response"]') as HTMLInputElement;
if (sig != null) {
this.token = sig.value;
await this.submit();
}
},
});
}, 0);
break;
case TwoFactorProviderType.Email:
this.twoFactorEmail = providerData.Email;
if (this.authService.twoFactorProvidersData.size > 1) {
await this.sendEmail(false);
}
break;
default:
break;
}
async submit() {
if (this.token == null || this.token === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("verificationCodeRequired")
);
return;
}
async submit() {
if (this.token == null || this.token === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('verificationCodeRequired'));
return;
}
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn) {
if (this.webAuthn != null) {
this.webAuthn.stop();
} else {
return;
}
} else if (this.selectedProviderType === TwoFactorProviderType.Email ||
this.selectedProviderType === TwoFactorProviderType.Authenticator) {
this.token = this.token.replace(' ', '').trim();
}
try {
await this.doSubmit();
} catch {
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && this.webAuthn != null) {
this.webAuthn.start();
}
}
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn) {
if (this.webAuthn != null) {
this.webAuthn.stop();
} else {
return;
}
} else if (
this.selectedProviderType === TwoFactorProviderType.Email ||
this.selectedProviderType === TwoFactorProviderType.Authenticator
) {
this.token = this.token.replace(" ", "").trim();
}
async doSubmit() {
this.formPromise = this.authService.logInTwoFactor(this.selectedProviderType, this.token, this.remember);
const response: AuthResult = await this.formPromise;
const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.setDisableFavicon(!!disableFavicon);
if (this.onSuccessfulLogin != null) {
this.onSuccessfulLogin();
}
if (response.resetMasterPassword) {
this.successRoute = 'set-password';
}
if (response.forcePasswordReset) {
this.successRoute = 'update-temp-password';
}
if (this.onSuccessfulLoginNavigate != null) {
this.onSuccessfulLoginNavigate();
} else {
this.router.navigate([this.successRoute], {
queryParams: {
identifier: this.identifier,
},
});
}
try {
await this.doSubmit();
} catch {
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && this.webAuthn != null) {
this.webAuthn.start();
}
}
}
async doSubmit() {
this.formPromise = this.authService.logInTwoFactor(
this.selectedProviderType,
this.token,
this.remember
);
const response: AuthResult = await this.formPromise;
const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.setDisableFavicon(!!disableFavicon);
if (this.onSuccessfulLogin != null) {
this.onSuccessfulLogin();
}
if (response.resetMasterPassword) {
this.successRoute = "set-password";
}
if (response.forcePasswordReset) {
this.successRoute = "update-temp-password";
}
if (this.onSuccessfulLoginNavigate != null) {
this.onSuccessfulLoginNavigate();
} else {
this.router.navigate([this.successRoute], {
queryParams: {
identifier: this.identifier,
},
});
}
}
async sendEmail(doToast: boolean) {
if (this.selectedProviderType !== TwoFactorProviderType.Email) {
return;
}
async sendEmail(doToast: boolean) {
if (this.selectedProviderType !== TwoFactorProviderType.Email) {
return;
}
if (this.emailPromise != null) {
return;
}
try {
const request = new TwoFactorEmailRequest();
request.email = this.authService.email;
request.masterPasswordHash = this.authService.masterPasswordHash;
this.emailPromise = this.apiService.postTwoFactorEmail(request);
await this.emailPromise;
if (doToast) {
this.platformUtilsService.showToast('success', null,
this.i18nService.t('verificationCodeEmailSent', this.twoFactorEmail));
}
} catch (e) {
this.logService.error(e);
}
this.emailPromise = null;
if (this.emailPromise != null) {
return;
}
authWebAuthn() {
const providerData = this.authService.twoFactorProvidersData.get(this.selectedProviderType);
if (!this.webAuthnSupported || this.webAuthn == null) {
return;
}
this.webAuthn.init(providerData);
try {
const request = new TwoFactorEmailRequest();
request.email = this.authService.email;
request.masterPasswordHash = this.authService.masterPasswordHash;
this.emailPromise = this.apiService.postTwoFactorEmail(request);
await this.emailPromise;
if (doToast) {
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("verificationCodeEmailSent", this.twoFactorEmail)
);
}
} catch (e) {
this.logService.error(e);
}
private cleanupWebAuthn() {
if (this.webAuthn != null) {
this.webAuthn.stop();
this.webAuthn.cleanup();
}
this.emailPromise = null;
}
authWebAuthn() {
const providerData = this.authService.twoFactorProvidersData.get(this.selectedProviderType);
if (!this.webAuthnSupported || this.webAuthn == null) {
return;
}
get authing(): boolean {
return this.authService.authingWithPassword() || this.authService.authingWithSso() || this.authService.authingWithApiKey();
}
this.webAuthn.init(providerData);
}
get needsLock(): boolean {
return this.authService.authingWithSso() || this.authService.authingWithApiKey();
private cleanupWebAuthn() {
if (this.webAuthn != null) {
this.webAuthn.stop();
this.webAuthn.cleanup();
}
}
get authing(): boolean {
return (
this.authService.authingWithPassword() ||
this.authService.authingWithSso() ||
this.authService.authingWithApiKey()
);
}
get needsLock(): boolean {
return this.authService.authingWithSso() || this.authService.authingWithApiKey();
}
}

View File

@@ -1,109 +1,134 @@
import { Directive } from '@angular/core';
import { Directive } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { ChangePasswordComponent as BaseChangePasswordComponent } from './change-password.component';
import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component";
import { EncString } from 'jslib-common/models/domain/encString';
import { MasterPasswordPolicyOptions } from 'jslib-common/models/domain/masterPasswordPolicyOptions';
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
import { EncString } from "jslib-common/models/domain/encString";
import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPasswordPolicyOptions";
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
import { UpdateTempPasswordRequest } from 'jslib-common/models/request/updateTempPasswordRequest';
import { UpdateTempPasswordRequest } from "jslib-common/models/request/updateTempPasswordRequest";
@Directive()
export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
hint: string;
key: string;
enforcedPolicyOptions: MasterPasswordPolicyOptions;
showPassword: boolean = false;
hint: string;
key: string;
enforcedPolicyOptions: MasterPasswordPolicyOptions;
showPassword: boolean = false;
onSuccessfulChangePassword: () => Promise<any>;
onSuccessfulChangePassword: () => Promise<any>;
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService, policyService: PolicyService,
cryptoService: CryptoService, messagingService: MessagingService,
private apiService: ApiService, stateService: StateService,
private syncService: SyncService, private logService: LogService) {
super(i18nService, cryptoService, messagingService, passwordGenerationService,
platformUtilsService, policyService, stateService);
constructor(
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService,
policyService: PolicyService,
cryptoService: CryptoService,
messagingService: MessagingService,
private apiService: ApiService,
stateService: StateService,
private syncService: SyncService,
private logService: LogService
) {
super(
i18nService,
cryptoService,
messagingService,
passwordGenerationService,
platformUtilsService,
policyService,
stateService
);
}
async ngOnInit() {
await this.syncService.fullSync(true);
super.ngOnInit();
}
togglePassword(confirmField: boolean) {
this.showPassword = !this.showPassword;
document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus();
}
async setupSubmitActions(): Promise<boolean> {
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
this.email = await this.stateService.getEmail();
this.kdf = await this.stateService.getKdfType();
this.kdfIterations = await this.stateService.getKdfIterations();
return true;
}
async submit() {
// Validation
if (!(await this.strongPassword())) {
return;
}
async ngOnInit() {
await this.syncService.fullSync(true);
super.ngOnInit();
if (!(await this.setupSubmitActions())) {
return;
}
togglePassword(confirmField: boolean) {
this.showPassword = !this.showPassword;
document.getElementById(confirmField ? 'masterPasswordRetype' : 'masterPassword').focus();
try {
// Create new key and hash new password
const newKey = await this.cryptoService.makeKey(
this.masterPassword,
this.email.trim().toLowerCase(),
this.kdf,
this.kdfIterations
);
const newPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, newKey);
// Grab user's current enc key
const userEncKey = await this.cryptoService.getEncKey();
// Create new encKey for the User
const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey);
await this.performSubmitActions(newPasswordHash, newKey, newEncKey);
} catch (e) {
this.logService.error(e);
}
}
async setupSubmitActions(): Promise<boolean> {
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
this.email = await this.stateService.getEmail();
this.kdf = await this.stateService.getKdfType();
this.kdfIterations = await this.stateService.getKdfIterations();
return true;
}
async submit() {
// Validation
if (!await this.strongPassword()) {
return;
}
if (!await this.setupSubmitActions()) {
return;
}
try {
// Create new key and hash new password
const newKey = await this.cryptoService.makeKey(this.masterPassword, this.email.trim().toLowerCase(),
this.kdf, this.kdfIterations);
const newPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, newKey);
// Grab user's current enc key
const userEncKey = await this.cryptoService.getEncKey();
// Create new encKey for the User
const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey);
await this.performSubmitActions(newPasswordHash, newKey, newEncKey);
} catch (e) {
this.logService.error(e);
}
}
async performSubmitActions(masterPasswordHash: string, key: SymmetricCryptoKey,
encKey: [SymmetricCryptoKey, EncString]) {
try {
// Create request
const request = new UpdateTempPasswordRequest();
request.key = encKey[1].encryptedString;
request.newMasterPasswordHash = masterPasswordHash;
request.masterPasswordHint = this.hint;
// Update user's password
this.formPromise = this.apiService.putUpdateTempPassword(request);
await this.formPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('updatedMasterPassword'));
if (this.onSuccessfulChangePassword != null) {
this.onSuccessfulChangePassword();
} else {
this.messagingService.send('logout');
}
} catch (e) {
this.logService.error(e);
}
async performSubmitActions(
masterPasswordHash: string,
key: SymmetricCryptoKey,
encKey: [SymmetricCryptoKey, EncString]
) {
try {
// Create request
const request = new UpdateTempPasswordRequest();
request.key = encKey[1].encryptedString;
request.newMasterPasswordHash = masterPasswordHash;
request.masterPasswordHint = this.hint;
// Update user's password
this.formPromise = this.apiService.putUpdateTempPassword(request);
await this.formPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("updatedMasterPassword")
);
if (this.onSuccessfulChangePassword != null) {
this.onSuccessfulChangePassword();
} else {
this.messagingService.send("logout");
}
} catch (e) {
this.logService.error(e);
}
}
}

View File

@@ -1,25 +1,46 @@
<ng-container *ngIf="!usesKeyConnector">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="password" name="MasterPasswordHash" class="form-control"
[formControl]="secret" required appAutofocus appInputVerbatim>
<small class="form-text text-muted">{{'confirmIdentity' | i18n}}</small>
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="password"
name="MasterPasswordHash"
class="form-control"
[formControl]="secret"
required
appAutofocus
appInputVerbatim
/>
<small class="form-text text-muted">{{ "confirmIdentity" | i18n }}</small>
</ng-container>
<ng-container *ngIf="usesKeyConnector">
<div class="form-group">
<label class="d-block">{{'sendVerificationCode' | i18n}}</label>
<button type="button" class="btn btn-outline-secondary" (click)="requestOTP()" [disabled]="disableRequestOTP">
{{'sendCode' | i18n}}
</button>
<span class="ml-2 text-success" role="alert" @sent *ngIf="sentCode">
<i class="fa fa-check-circle-o" aria-hidden="true"></i>
{{'codeSent' | i18n}}
</span>
</div>
<div class="form-group">
<label class="d-block">{{ "sendVerificationCode" | i18n }}</label>
<button
type="button"
class="btn btn-outline-secondary"
(click)="requestOTP()"
[disabled]="disableRequestOTP"
>
{{ "sendCode" | i18n }}
</button>
<span class="ml-2 text-success" role="alert" @sent *ngIf="sentCode">
<i class="fa fa-check-circle-o" aria-hidden="true"></i>
{{ "codeSent" | i18n }}
</span>
</div>
<div class="form-group">
<label for="verificationCode">{{'verificationCode' | i18n}}</label>
<input id="verificationCode" type="input" name="verificationCode" class="form-control"
[formControl]="secret" required appAutofocus appInputVerbatim>
<small class="form-text text-muted">{{'confirmIdentity' | i18n}}</small>
</div>
<div class="form-group">
<label for="verificationCode">{{ "verificationCode" | i18n }}</label>
<input
id="verificationCode"
type="input"
name="verificationCode"
class="form-control"
[formControl]="secret"
required
appAutofocus
appInputVerbatim
/>
<small class="form-text text-muted">{{ "confirmIdentity" | i18n }}</small>
</div>
</ng-container>

View File

@@ -1,105 +1,92 @@
import {
animate,
style,
transition,
trigger,
} from '@angular/animations';
import {
Component,
OnInit,
} from '@angular/core';
import {
ControlValueAccessor,
FormControl,
NG_VALUE_ACCESSOR,
} from '@angular/forms';
import { animate, style, transition, trigger } from "@angular/animations";
import { Component, OnInit } from "@angular/core";
import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from "@angular/forms";
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
import { UserVerificationService } from 'jslib-common/abstractions/userVerification.service';
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { UserVerificationService } from "jslib-common/abstractions/userVerification.service";
import { VerificationType } from 'jslib-common/enums/verificationType';
import { VerificationType } from "jslib-common/enums/verificationType";
import { Verification } from 'jslib-common/types/verification';
import { Verification } from "jslib-common/types/verification";
@Component({
selector: 'app-verify-master-password',
templateUrl: 'verify-master-password.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: VerifyMasterPasswordComponent,
},
],
animations: [
trigger('sent', [
transition(':enter', [
style({ opacity: 0 }),
animate('100ms', style({ opacity: 1 })),
]),
]),
],
selector: "app-verify-master-password",
templateUrl: "verify-master-password.component.html",
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: VerifyMasterPasswordComponent,
},
],
animations: [
trigger("sent", [
transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]),
]),
],
})
export class VerifyMasterPasswordComponent implements ControlValueAccessor, OnInit {
usesKeyConnector: boolean = false;
disableRequestOTP: boolean = false;
sentCode: boolean = false;
usesKeyConnector: boolean = false;
disableRequestOTP: boolean = false;
sentCode: boolean = false;
secret = new FormControl('');
secret = new FormControl("");
private onChange: (value: Verification) => void;
private onChange: (value: Verification) => void;
constructor(private keyConnectorService: KeyConnectorService,
private userVerificationService: UserVerificationService) { }
constructor(
private keyConnectorService: KeyConnectorService,
private userVerificationService: UserVerificationService
) {}
async ngOnInit() {
this.usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
this.processChanges(this.secret.value);
async ngOnInit() {
this.usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
this.processChanges(this.secret.value);
this.secret.valueChanges.subscribe(secret => this.processChanges(secret));
this.secret.valueChanges.subscribe((secret) => this.processChanges(secret));
}
async requestOTP() {
if (this.usesKeyConnector) {
this.disableRequestOTP = true;
try {
await this.userVerificationService.requestOTP();
this.sentCode = true;
} finally {
this.disableRequestOTP = false;
}
}
}
writeValue(obj: any): void {
this.secret.setValue(obj);
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
// Not implemented
}
setDisabledState?(isDisabled: boolean): void {
this.disableRequestOTP = isDisabled;
if (isDisabled) {
this.secret.disable();
} else {
this.secret.enable();
}
}
private processChanges(secret: string) {
if (this.onChange == null) {
return;
}
async requestOTP() {
if (this.usesKeyConnector) {
this.disableRequestOTP = true;
try {
await this.userVerificationService.requestOTP();
this.sentCode = true;
} finally {
this.disableRequestOTP = false;
}
}
}
writeValue(obj: any): void {
this.secret.setValue(obj);
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
// Not implemented
}
setDisabledState?(isDisabled: boolean): void {
this.disableRequestOTP = isDisabled;
if (isDisabled) {
this.secret.disable();
} else {
this.secret.enable();
}
}
private processChanges(secret: string) {
if (this.onChange == null) {
return;
}
this.onChange({
type: this.usesKeyConnector ? VerificationType.OTP : VerificationType.MasterPassword,
secret: secret,
});
}
this.onChange({
type: this.usesKeyConnector ? VerificationType.OTP : VerificationType.MasterPassword,
secret: secret,
});
}
}

View File

@@ -1,35 +1,32 @@
import {
Directive,
Input,
} from '@angular/core';
import { Directive, Input } from "@angular/core";
import { EventType } from 'jslib-common/enums/eventType';
import { FieldType } from 'jslib-common/enums/fieldType';
import { EventType } from "jslib-common/enums/eventType";
import { FieldType } from "jslib-common/enums/fieldType";
import { EventService } from 'jslib-common/abstractions/event.service';
import { EventService } from "jslib-common/abstractions/event.service";
import { CipherView } from 'jslib-common/models/view/cipherView';
import { FieldView } from 'jslib-common/models/view/fieldView';
import { CipherView } from "jslib-common/models/view/cipherView";
import { FieldView } from "jslib-common/models/view/fieldView";
@Directive()
export class ViewCustomFieldsComponent {
@Input() cipher: CipherView;
@Input() promptPassword: () => Promise<boolean>;
@Input() copy: (value: string, typeI18nKey: string, aType: string) => void;
@Input() cipher: CipherView;
@Input() promptPassword: () => Promise<boolean>;
@Input() copy: (value: string, typeI18nKey: string, aType: string) => void;
fieldType = FieldType;
fieldType = FieldType;
constructor(private eventService: EventService) { }
constructor(private eventService: EventService) {}
async toggleFieldValue(field: FieldView) {
if (!await this.promptPassword()) {
return;
}
const f = (field as any);
f.showValue = !f.showValue;
if (f.showValue) {
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipher.id);
}
async toggleFieldValue(field: FieldView) {
if (!(await this.promptPassword())) {
return;
}
const f = field as any;
f.showValue = !f.showValue;
if (f.showValue) {
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipher.id);
}
}
}

View File

@@ -1,394 +1,447 @@
import {
ChangeDetectorRef,
Directive,
EventEmitter,
Input,
NgZone,
OnDestroy,
OnInit,
Output,
} from '@angular/core';
ChangeDetectorRef,
Directive,
EventEmitter,
Input,
NgZone,
OnDestroy,
OnInit,
Output,
} from "@angular/core";
import { CipherRepromptType } from 'jslib-common/enums/cipherRepromptType';
import { CipherType } from 'jslib-common/enums/cipherType';
import { EventType } from 'jslib-common/enums/eventType';
import { FieldType } from 'jslib-common/enums/fieldType';
import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType";
import { CipherType } from "jslib-common/enums/cipherType";
import { EventType } from "jslib-common/enums/eventType";
import { FieldType } from "jslib-common/enums/fieldType";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuditService } from 'jslib-common/abstractions/audit.service';
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EventService } from 'jslib-common/abstractions/event.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { TokenService } from 'jslib-common/abstractions/token.service';
import { TotpService } from 'jslib-common/abstractions/totp.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuditService } from "jslib-common/abstractions/audit.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EventService } from "jslib-common/abstractions/event.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { TokenService } from "jslib-common/abstractions/token.service";
import { TotpService } from "jslib-common/abstractions/totp.service";
import { ErrorResponse } from 'jslib-common/models/response/errorResponse';
import { ErrorResponse } from "jslib-common/models/response/errorResponse";
import { AttachmentView } from 'jslib-common/models/view/attachmentView';
import { CipherView } from 'jslib-common/models/view/cipherView';
import { LoginUriView } from 'jslib-common/models/view/loginUriView';
import { AttachmentView } from "jslib-common/models/view/attachmentView";
import { CipherView } from "jslib-common/models/view/cipherView";
import { LoginUriView } from "jslib-common/models/view/loginUriView";
const BroadcasterSubscriptionId = 'ViewComponent';
const BroadcasterSubscriptionId = "ViewComponent";
@Directive()
export class ViewComponent implements OnDestroy, OnInit {
@Input() cipherId: string;
@Output() onEditCipher = new EventEmitter<CipherView>();
@Output() onCloneCipher = new EventEmitter<CipherView>();
@Output() onShareCipher = new EventEmitter<CipherView>();
@Output() onDeletedCipher = new EventEmitter<CipherView>();
@Output() onRestoredCipher = new EventEmitter<CipherView>();
@Input() cipherId: string;
@Output() onEditCipher = new EventEmitter<CipherView>();
@Output() onCloneCipher = new EventEmitter<CipherView>();
@Output() onShareCipher = new EventEmitter<CipherView>();
@Output() onDeletedCipher = new EventEmitter<CipherView>();
@Output() onRestoredCipher = new EventEmitter<CipherView>();
cipher: CipherView;
showPassword: boolean;
showCardNumber: boolean;
showCardCode: boolean;
canAccessPremium: boolean;
totpCode: string;
totpCodeFormatted: string;
totpDash: number;
totpSec: number;
totpLow: boolean;
fieldType = FieldType;
checkPasswordPromise: Promise<number>;
cipher: CipherView;
showPassword: boolean;
showCardNumber: boolean;
showCardCode: boolean;
canAccessPremium: boolean;
totpCode: string;
totpCodeFormatted: string;
totpDash: number;
totpSec: number;
totpLow: boolean;
fieldType = FieldType;
checkPasswordPromise: Promise<number>;
private totpInterval: any;
private previousCipherId: string;
private passwordReprompted: boolean = false;
private totpInterval: any;
private previousCipherId: string;
private passwordReprompted: boolean = false;
constructor(protected cipherService: CipherService, protected totpService: TotpService,
protected tokenService: TokenService, protected i18nService: I18nService,
protected cryptoService: CryptoService, protected platformUtilsService: PlatformUtilsService,
protected auditService: AuditService, protected win: Window,
protected broadcasterService: BroadcasterService, protected ngZone: NgZone,
protected changeDetectorRef: ChangeDetectorRef, protected eventService: EventService,
protected apiService: ApiService, protected passwordRepromptService: PasswordRepromptService,
private logService: LogService, protected stateService: StateService) { }
constructor(
protected cipherService: CipherService,
protected totpService: TotpService,
protected tokenService: TokenService,
protected i18nService: I18nService,
protected cryptoService: CryptoService,
protected platformUtilsService: PlatformUtilsService,
protected auditService: AuditService,
protected win: Window,
protected broadcasterService: BroadcasterService,
protected ngZone: NgZone,
protected changeDetectorRef: ChangeDetectorRef,
protected eventService: EventService,
protected apiService: ApiService,
protected passwordRepromptService: PasswordRepromptService,
private logService: LogService,
protected stateService: StateService
) {}
ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case 'syncCompleted':
if (message.successfully) {
await this.load();
this.changeDetectorRef.detectChanges();
}
break;
}
});
});
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
this.cleanUp();
}
async load() {
this.cleanUp();
const cipher = await this.cipherService.get(this.cipherId);
this.cipher = await cipher.decrypt();
this.canAccessPremium = await this.stateService.getCanAccessPremium();
if (this.cipher.type === CipherType.Login && this.cipher.login.totp &&
(cipher.organizationUseTotp || this.canAccessPremium)) {
await this.totpUpdateCode();
const interval = this.totpService.getTimeInterval(this.cipher.login.totp);
await this.totpTick(interval);
this.totpInterval = setInterval(async () => {
await this.totpTick(interval);
}, 1000);
}
if (this.previousCipherId !== this.cipherId) {
this.eventService.collect(EventType.Cipher_ClientViewed, this.cipherId);
}
this.previousCipherId = this.cipherId;
}
async edit() {
if (await this.promptPassword()) {
this.onEditCipher.emit(this.cipher);
return true;
}
return false;
}
async clone() {
if (await this.promptPassword()) {
this.onCloneCipher.emit(this.cipher);
return true;
}
return false;
}
async share() {
if (await this.promptPassword()) {
this.onShareCipher.emit(this.cipher);
return true;
}
return false;
}
async delete(): Promise<boolean> {
if (!await this.promptPassword()) {
return;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t(this.cipher.isDeleted ? 'permanentlyDeleteItemConfirmation' : 'deleteItemConfirmation'),
this.i18nService.t('deleteItem'), this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
try {
await this.deleteCipher();
this.platformUtilsService.showToast('success', null,
this.i18nService.t(this.cipher.isDeleted ? 'permanentlyDeletedItem' : 'deletedItem'));
this.onDeletedCipher.emit(this.cipher);
} catch (e) {
this.logService.error(e);
}
return true;
}
async restore(): Promise<boolean> {
if (!this.cipher.isDeleted) {
return false;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('restoreItemConfirmation'), this.i18nService.t('restoreItem'),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
try {
await this.restoreCipher();
this.platformUtilsService.showToast('success', null, this.i18nService.t('restoredItem'));
this.onRestoredCipher.emit(this.cipher);
} catch (e) {
this.logService.error(e);
}
return true;
}
async togglePassword() {
if (!await this.promptPassword()) {
return;
}
this.showPassword = !this.showPassword;
if (this.showPassword) {
this.eventService.collect(EventType.Cipher_ClientToggledPasswordVisible, this.cipherId);
}
}
async toggleCardNumber() {
if (!await this.promptPassword()) {
return;
}
this.showCardNumber = !this.showCardNumber;
if (this.showCardNumber) {
this.eventService.collect(EventType.Cipher_ClientToggledCardCodeVisible, this.cipherId);
}
}
async toggleCardCode() {
if (!await this.promptPassword()) {
return;
}
this.showCardCode = !this.showCardCode;
if (this.showCardCode) {
this.eventService.collect(EventType.Cipher_ClientToggledCardCodeVisible, this.cipherId);
}
}
async checkPassword() {
if (this.cipher.login == null || this.cipher.login.password == null || this.cipher.login.password === '') {
return;
}
this.checkPasswordPromise = this.auditService.passwordLeaked(this.cipher.login.password);
const matches = await this.checkPasswordPromise;
if (matches > 0) {
this.platformUtilsService.showToast('warning', null,
this.i18nService.t('passwordExposed', matches.toString()));
} else {
this.platformUtilsService.showToast('success', null, this.i18nService.t('passwordSafe'));
}
}
launch(uri: LoginUriView, cipherId?: string) {
if (!uri.canLaunch) {
return;
}
if (cipherId) {
this.cipherService.updateLastLaunchedDate(cipherId);
}
this.platformUtilsService.launchUri(uri.launchUri);
}
async copy(value: string, typeI18nKey: string, aType: string) {
if (value == null) {
return;
}
if (this.passwordRepromptService.protectedFields().includes(aType) && !await this.promptPassword()) {
return;
}
const copyOptions = this.win != null ? { window: this.win } : null;
this.platformUtilsService.copyToClipboard(value, copyOptions);
this.platformUtilsService.showToast('info', null,
this.i18nService.t('valueCopied', this.i18nService.t(typeI18nKey)));
if (typeI18nKey === 'password') {
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipherId);
} else if (typeI18nKey === 'securityCode') {
this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, this.cipherId);
} else if (aType === 'H_Field') {
this.eventService.collect(EventType.Cipher_ClientCopiedHiddenField, this.cipherId);
}
}
setTextDataOnDrag(event: DragEvent, data: string) {
event.dataTransfer.setData('text', data);
}
async downloadAttachment(attachment: AttachmentView) {
if (!await this.promptPassword()) {
return;
}
const a = (attachment as any);
if (a.downloading) {
return;
}
if (this.cipher.organizationId == null && !this.canAccessPremium) {
this.platformUtilsService.showToast('error', this.i18nService.t('premiumRequired'),
this.i18nService.t('premiumRequiredDesc'));
return;
}
let url: string;
try {
const attachmentDownloadResponse = await this.apiService.getAttachmentData(this.cipher.id, attachment.id);
url = attachmentDownloadResponse.url;
} catch (e) {
if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) {
url = attachment.url;
} else if (e instanceof ErrorResponse) {
throw new Error((e as ErrorResponse).getSingleMessage());
} else {
throw e;
ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case "syncCompleted":
if (message.successfully) {
await this.load();
this.changeDetectorRef.detectChanges();
}
break;
}
});
});
}
a.downloading = true;
const response = await fetch(new Request(url, { cache: 'no-store' }));
if (response.status !== 200) {
this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred'));
a.downloading = false;
return;
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
this.cleanUp();
}
try {
const buf = await response.arrayBuffer();
const key = attachment.key != null ? attachment.key :
await this.cryptoService.getOrgKey(this.cipher.organizationId);
const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
this.platformUtilsService.saveFile(this.win, decBuf, null, attachment.fileName);
} catch (e) {
this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred'));
}
async load() {
this.cleanUp();
a.downloading = false;
const cipher = await this.cipherService.get(this.cipherId);
this.cipher = await cipher.decrypt();
this.canAccessPremium = await this.stateService.getCanAccessPremium();
if (
this.cipher.type === CipherType.Login &&
this.cipher.login.totp &&
(cipher.organizationUseTotp || this.canAccessPremium)
) {
await this.totpUpdateCode();
const interval = this.totpService.getTimeInterval(this.cipher.login.totp);
await this.totpTick(interval);
this.totpInterval = setInterval(async () => {
await this.totpTick(interval);
}, 1000);
}
protected deleteCipher() {
return this.cipher.isDeleted ? this.cipherService.deleteWithServer(this.cipher.id)
: this.cipherService.softDeleteWithServer(this.cipher.id);
if (this.previousCipherId !== this.cipherId) {
this.eventService.collect(EventType.Cipher_ClientViewed, this.cipherId);
}
this.previousCipherId = this.cipherId;
}
async edit() {
if (await this.promptPassword()) {
this.onEditCipher.emit(this.cipher);
return true;
}
protected restoreCipher() {
return this.cipherService.restoreWithServer(this.cipher.id);
return false;
}
async clone() {
if (await this.promptPassword()) {
this.onCloneCipher.emit(this.cipher);
return true;
}
protected async promptPassword() {
if (this.cipher.reprompt === CipherRepromptType.None || this.passwordReprompted) {
return true;
}
return false;
}
return this.passwordReprompted = await this.passwordRepromptService.showPasswordPrompt();
async share() {
if (await this.promptPassword()) {
this.onShareCipher.emit(this.cipher);
return true;
}
private cleanUp() {
this.totpCode = null;
this.cipher = null;
this.showPassword = false;
this.showCardNumber = false;
this.showCardCode = false;
this.passwordReprompted = false;
if (this.totpInterval) {
clearInterval(this.totpInterval);
}
return false;
}
async delete(): Promise<boolean> {
if (!(await this.promptPassword())) {
return;
}
private async totpUpdateCode() {
if (this.cipher == null || this.cipher.type !== CipherType.Login || this.cipher.login.totp == null) {
if (this.totpInterval) {
clearInterval(this.totpInterval);
}
return;
}
this.totpCode = await this.totpService.getCode(this.cipher.login.totp);
if (this.totpCode != null) {
if (this.totpCode.length > 4) {
const half = Math.floor(this.totpCode.length / 2);
this.totpCodeFormatted = this.totpCode.substring(0, half) + ' ' + this.totpCode.substring(half);
} else {
this.totpCodeFormatted = this.totpCode;
}
} else {
this.totpCodeFormatted = null;
if (this.totpInterval) {
clearInterval(this.totpInterval);
}
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t(
this.cipher.isDeleted ? "permanentlyDeleteItemConfirmation" : "deleteItemConfirmation"
),
this.i18nService.t("deleteItem"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
private async totpTick(intervalSeconds: number) {
const epoch = Math.round(new Date().getTime() / 1000.0);
const mod = epoch % intervalSeconds;
this.totpSec = intervalSeconds - mod;
this.totpDash = +(Math.round((((78.6 / intervalSeconds) * mod) + 'e+2') as any) + 'e-2');
this.totpLow = this.totpSec <= 7;
if (mod === 0) {
await this.totpUpdateCode();
}
try {
await this.deleteCipher();
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t(this.cipher.isDeleted ? "permanentlyDeletedItem" : "deletedItem")
);
this.onDeletedCipher.emit(this.cipher);
} catch (e) {
this.logService.error(e);
}
return true;
}
async restore(): Promise<boolean> {
if (!this.cipher.isDeleted) {
return false;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("restoreItemConfirmation"),
this.i18nService.t("restoreItem"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
try {
await this.restoreCipher();
this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItem"));
this.onRestoredCipher.emit(this.cipher);
} catch (e) {
this.logService.error(e);
}
return true;
}
async togglePassword() {
if (!(await this.promptPassword())) {
return;
}
this.showPassword = !this.showPassword;
if (this.showPassword) {
this.eventService.collect(EventType.Cipher_ClientToggledPasswordVisible, this.cipherId);
}
}
async toggleCardNumber() {
if (!(await this.promptPassword())) {
return;
}
this.showCardNumber = !this.showCardNumber;
if (this.showCardNumber) {
this.eventService.collect(EventType.Cipher_ClientToggledCardCodeVisible, this.cipherId);
}
}
async toggleCardCode() {
if (!(await this.promptPassword())) {
return;
}
this.showCardCode = !this.showCardCode;
if (this.showCardCode) {
this.eventService.collect(EventType.Cipher_ClientToggledCardCodeVisible, this.cipherId);
}
}
async checkPassword() {
if (
this.cipher.login == null ||
this.cipher.login.password == null ||
this.cipher.login.password === ""
) {
return;
}
this.checkPasswordPromise = this.auditService.passwordLeaked(this.cipher.login.password);
const matches = await this.checkPasswordPromise;
if (matches > 0) {
this.platformUtilsService.showToast(
"warning",
null,
this.i18nService.t("passwordExposed", matches.toString())
);
} else {
this.platformUtilsService.showToast("success", null, this.i18nService.t("passwordSafe"));
}
}
launch(uri: LoginUriView, cipherId?: string) {
if (!uri.canLaunch) {
return;
}
if (cipherId) {
this.cipherService.updateLastLaunchedDate(cipherId);
}
this.platformUtilsService.launchUri(uri.launchUri);
}
async copy(value: string, typeI18nKey: string, aType: string) {
if (value == null) {
return;
}
if (
this.passwordRepromptService.protectedFields().includes(aType) &&
!(await this.promptPassword())
) {
return;
}
const copyOptions = this.win != null ? { window: this.win } : null;
this.platformUtilsService.copyToClipboard(value, copyOptions);
this.platformUtilsService.showToast(
"info",
null,
this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey))
);
if (typeI18nKey === "password") {
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipherId);
} else if (typeI18nKey === "securityCode") {
this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, this.cipherId);
} else if (aType === "H_Field") {
this.eventService.collect(EventType.Cipher_ClientCopiedHiddenField, this.cipherId);
}
}
setTextDataOnDrag(event: DragEvent, data: string) {
event.dataTransfer.setData("text", data);
}
async downloadAttachment(attachment: AttachmentView) {
if (!(await this.promptPassword())) {
return;
}
const a = attachment as any;
if (a.downloading) {
return;
}
if (this.cipher.organizationId == null && !this.canAccessPremium) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("premiumRequired"),
this.i18nService.t("premiumRequiredDesc")
);
return;
}
let url: string;
try {
const attachmentDownloadResponse = await this.apiService.getAttachmentData(
this.cipher.id,
attachment.id
);
url = attachmentDownloadResponse.url;
} catch (e) {
if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) {
url = attachment.url;
} else if (e instanceof ErrorResponse) {
throw new Error((e as ErrorResponse).getSingleMessage());
} else {
throw e;
}
}
a.downloading = true;
const response = await fetch(new Request(url, { cache: "no-store" }));
if (response.status !== 200) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
a.downloading = false;
return;
}
try {
const buf = await response.arrayBuffer();
const key =
attachment.key != null
? attachment.key
: await this.cryptoService.getOrgKey(this.cipher.organizationId);
const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
this.platformUtilsService.saveFile(this.win, decBuf, null, attachment.fileName);
} catch (e) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
}
a.downloading = false;
}
protected deleteCipher() {
return this.cipher.isDeleted
? this.cipherService.deleteWithServer(this.cipher.id)
: this.cipherService.softDeleteWithServer(this.cipher.id);
}
protected restoreCipher() {
return this.cipherService.restoreWithServer(this.cipher.id);
}
protected async promptPassword() {
if (this.cipher.reprompt === CipherRepromptType.None || this.passwordReprompted) {
return true;
}
return (this.passwordReprompted = await this.passwordRepromptService.showPasswordPrompt());
}
private cleanUp() {
this.totpCode = null;
this.cipher = null;
this.showPassword = false;
this.showCardNumber = false;
this.showCardCode = false;
this.passwordReprompted = false;
if (this.totpInterval) {
clearInterval(this.totpInterval);
}
}
private async totpUpdateCode() {
if (
this.cipher == null ||
this.cipher.type !== CipherType.Login ||
this.cipher.login.totp == null
) {
if (this.totpInterval) {
clearInterval(this.totpInterval);
}
return;
}
this.totpCode = await this.totpService.getCode(this.cipher.login.totp);
if (this.totpCode != null) {
if (this.totpCode.length > 4) {
const half = Math.floor(this.totpCode.length / 2);
this.totpCodeFormatted =
this.totpCode.substring(0, half) + " " + this.totpCode.substring(half);
} else {
this.totpCodeFormatted = this.totpCode;
}
} else {
this.totpCodeFormatted = null;
if (this.totpInterval) {
clearInterval(this.totpInterval);
}
}
}
private async totpTick(intervalSeconds: number) {
const epoch = Math.round(new Date().getTime() / 1000.0);
const mod = epoch % intervalSeconds;
this.totpSec = intervalSeconds - mod;
this.totpDash = +(Math.round(((78.6 / intervalSeconds) * mod + "e+2") as any) + "e-2");
this.totpLow = this.totpSec <= 7;
if (mod === 0) {
await this.totpUpdateCode();
}
}
}

View File

@@ -1,28 +1,23 @@
import {
Directive,
ElementRef,
Input,
Renderer2,
} from '@angular/core';
import { Directive, ElementRef, Input, Renderer2 } from "@angular/core";
@Directive({
selector: '[appA11yTitle]',
selector: "[appA11yTitle]",
})
export class A11yTitleDirective {
@Input() set appA11yTitle(title: string) {
this.title = title;
@Input() set appA11yTitle(title: string) {
this.title = title;
}
private title: string;
constructor(private el: ElementRef, private renderer: Renderer2) {}
ngOnInit() {
if (!this.el.nativeElement.hasAttribute("title")) {
this.renderer.setAttribute(this.el.nativeElement, "title", this.title);
}
private title: string;
constructor(private el: ElementRef, private renderer: Renderer2) { }
ngOnInit() {
if (!this.el.nativeElement.hasAttribute('title')) {
this.renderer.setAttribute(this.el.nativeElement, 'title', this.title);
}
if (!this.el.nativeElement.hasAttribute('aria-label')) {
this.renderer.setAttribute(this.el.nativeElement, 'aria-label', this.title);
}
if (!this.el.nativeElement.hasAttribute("aria-label")) {
this.renderer.setAttribute(this.el.nativeElement, "aria-label", this.title);
}
}
}

View File

@@ -1,42 +1,46 @@
import {
Directive,
ElementRef,
Input,
OnChanges,
} from '@angular/core';
import { LogService } from 'jslib-common/abstractions/log.service';
import { Directive, ElementRef, Input, OnChanges } from "@angular/core";
import { LogService } from "jslib-common/abstractions/log.service";
import { ErrorResponse } from 'jslib-common/models/response/errorResponse';
import { ErrorResponse } from "jslib-common/models/response/errorResponse";
import { ValidationService } from '../services/validation.service';
import { ValidationService } from "../services/validation.service";
@Directive({
selector: '[appApiAction]',
selector: "[appApiAction]",
})
export class ApiActionDirective implements OnChanges {
@Input() appApiAction: Promise<any>;
@Input() appApiAction: Promise<any>;
constructor(private el: ElementRef, private validationService: ValidationService,
private logService: LogService) { }
constructor(
private el: ElementRef,
private validationService: ValidationService,
private logService: LogService
) {}
ngOnChanges(changes: any) {
if (this.appApiAction == null || this.appApiAction.then == null) {
return;
}
this.el.nativeElement.loading = true;
this.appApiAction.then((response: any) => {
this.el.nativeElement.loading = false;
}, (e: any) => {
this.el.nativeElement.loading = false;
if ((e instanceof ErrorResponse || e.constructor.name === 'ErrorResponse') && (e as ErrorResponse).captchaRequired) {
this.logService.error('Captcha required error response: ' + e.getSingleMessage());
return;
}
this.logService?.error(`Received API exception: ${e}`);
this.validationService.showError(e);
});
ngOnChanges(changes: any) {
if (this.appApiAction == null || this.appApiAction.then == null) {
return;
}
this.el.nativeElement.loading = true;
this.appApiAction.then(
(response: any) => {
this.el.nativeElement.loading = false;
},
(e: any) => {
this.el.nativeElement.loading = false;
if (
(e instanceof ErrorResponse || e.constructor.name === "ErrorResponse") &&
(e as ErrorResponse).captchaRequired
) {
this.logService.error("Captcha required error response: " + e.getSingleMessage());
return;
}
this.logService?.error(`Received API exception: ${e}`);
this.validationService.showError(e);
}
);
}
}

View File

@@ -1,33 +1,28 @@
import {
Directive,
ElementRef,
Input,
NgZone,
} from '@angular/core';
import { Directive, ElementRef, Input, NgZone } from "@angular/core";
import { take } from 'rxjs/operators';
import { take } from "rxjs/operators";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
@Directive({
selector: '[appAutofocus]',
selector: "[appAutofocus]",
})
export class AutofocusDirective {
@Input() set appAutofocus(condition: boolean | string) {
this.autofocus = condition === '' || condition === true;
}
private autofocus: boolean;
constructor(private el: ElementRef, private ngZone: NgZone) { }
ngOnInit() {
if (!Utils.isMobileBrowser && this.autofocus) {
if (this.ngZone.isStable) {
this.el.nativeElement.focus();
} else {
this.ngZone.onStable.pipe(take(1)).subscribe(() => this.el.nativeElement.focus());
}
}
@Input() set appAutofocus(condition: boolean | string) {
this.autofocus = condition === "" || condition === true;
}
private autofocus: boolean;
constructor(private el: ElementRef, private ngZone: NgZone) {}
ngOnInit() {
if (!Utils.isMobileBrowser && this.autofocus) {
if (this.ngZone.isStable) {
this.el.nativeElement.focus();
} else {
this.ngZone.onStable.pipe(take(1)).subscribe(() => this.el.nativeElement.focus());
}
}
}
}

View File

@@ -1,17 +1,12 @@
import {
Directive,
ElementRef,
HostListener,
} from '@angular/core';
import { Directive, ElementRef, HostListener } from "@angular/core";
@Directive({
selector: '[appBlurClick]',
selector: "[appBlurClick]",
})
export class BlurClickDirective {
constructor(private el: ElementRef) {
}
constructor(private el: ElementRef) {}
@HostListener('click') onClick() {
this.el.nativeElement.blur();
}
@HostListener("click") onClick() {
this.el.nativeElement.blur();
}
}

View File

@@ -1,51 +1,59 @@
import {
Directive,
ElementRef,
HostListener,
OnInit,
} from '@angular/core';
import { Directive, ElementRef, HostListener, OnInit } from "@angular/core";
@Directive({
selector: '[appBoxRow]',
selector: "[appBoxRow]",
})
export class BoxRowDirective implements OnInit {
el: HTMLElement = null;
formEls: Element[];
el: HTMLElement = null;
formEls: Element[];
constructor(private elRef: ElementRef) {
this.el = elRef.nativeElement;
constructor(private elRef: ElementRef) {
this.el = elRef.nativeElement;
}
ngOnInit(): void {
this.formEls = Array.from(
this.el.querySelectorAll('input:not([type="hidden"]), select, textarea')
);
this.formEls.forEach((formEl) => {
formEl.addEventListener(
"focus",
(event: Event) => {
this.el.classList.add("active");
},
false
);
formEl.addEventListener(
"blur",
(event: Event) => {
this.el.classList.remove("active");
},
false
);
});
}
@HostListener("click", ["$event"]) onClick(event: Event) {
const target = event.target as HTMLElement;
if (
target !== this.el &&
!target.classList.contains("progress") &&
!target.classList.contains("progress-bar")
) {
return;
}
ngOnInit(): void {
this.formEls = Array.from(this.el.querySelectorAll('input:not([type="hidden"]), select, textarea'));
this.formEls.forEach(formEl => {
formEl.addEventListener('focus', (event: Event) => {
this.el.classList.add('active');
}, false);
formEl.addEventListener('blur', (event: Event) => {
this.el.classList.remove('active');
}, false);
});
}
@HostListener('click', ['$event']) onClick(event: Event) {
const target = event.target as HTMLElement;
if (target !== this.el && !target.classList.contains('progress') &&
!target.classList.contains('progress-bar')) {
return;
}
if (this.formEls.length > 0) {
const formEl = (this.formEls[0] as HTMLElement);
if (formEl.tagName.toLowerCase() === 'input') {
const inputEl = (formEl as HTMLInputElement);
if (inputEl.type != null && inputEl.type.toLowerCase() === 'checkbox') {
inputEl.click();
return;
}
}
formEl.focus();
if (this.formEls.length > 0) {
const formEl = this.formEls[0] as HTMLElement;
if (formEl.tagName.toLowerCase() === "input") {
const inputEl = formEl as HTMLInputElement;
if (inputEl.type != null && inputEl.type.toLowerCase() === "checkbox") {
inputEl.click();
return;
}
}
formEl.focus();
}
}
}

View File

@@ -1,62 +1,76 @@
import {
CdkFixedSizeVirtualScroll,
FixedSizeVirtualScrollStrategy,
VIRTUAL_SCROLL_STRATEGY,
} from '@angular/cdk/scrolling';
import {
Directive,
forwardRef,
} from '@angular/core';
CdkFixedSizeVirtualScroll,
FixedSizeVirtualScrollStrategy,
VIRTUAL_SCROLL_STRATEGY,
} from "@angular/cdk/scrolling";
import { Directive, forwardRef } from "@angular/core";
// Custom virtual scroll strategy for cdk-virtual-scroll
// Uses a sample list item to set the itemSize for FixedSizeVirtualScrollStrategy
// The use case is the same as FixedSizeVirtualScrollStrategy, but it avoids locking in pixel sizes in the template.
export class CipherListVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy {
private checkItemSizeCallback: any;
private timeout: any;
private checkItemSizeCallback: any;
private timeout: any;
constructor(itemSize: number, minBufferPx: number, maxBufferPx: number, checkItemSizeCallback: any) {
super(itemSize, minBufferPx, maxBufferPx);
this.checkItemSizeCallback = checkItemSizeCallback;
constructor(
itemSize: number,
minBufferPx: number,
maxBufferPx: number,
checkItemSizeCallback: any
) {
super(itemSize, minBufferPx, maxBufferPx);
this.checkItemSizeCallback = checkItemSizeCallback;
}
onContentRendered() {
if (this.timeout != null) {
clearTimeout(this.timeout);
}
onContentRendered() {
if (this.timeout != null) {
clearTimeout(this.timeout);
}
this.timeout = setTimeout(this.checkItemSizeCallback, 500);
}
this.timeout = setTimeout(this.checkItemSizeCallback, 500);
}
}
export function _cipherListVirtualScrollStrategyFactory(cipherListDir: CipherListVirtualScroll) {
return cipherListDir._scrollStrategy;
return cipherListDir._scrollStrategy;
}
@Directive({
selector: 'cdk-virtual-scroll-viewport[itemSize]',
providers: [{
provide: VIRTUAL_SCROLL_STRATEGY,
useFactory: _cipherListVirtualScrollStrategyFactory,
deps: [forwardRef(() => CipherListVirtualScroll)],
}],
selector: "cdk-virtual-scroll-viewport[itemSize]",
providers: [
{
provide: VIRTUAL_SCROLL_STRATEGY,
useFactory: _cipherListVirtualScrollStrategyFactory,
deps: [forwardRef(() => CipherListVirtualScroll)],
},
],
})
export class CipherListVirtualScroll extends CdkFixedSizeVirtualScroll {
_scrollStrategy: CipherListVirtualScrollStrategy;
_scrollStrategy: CipherListVirtualScrollStrategy;
constructor() {
super();
this._scrollStrategy = new CipherListVirtualScrollStrategy(this.itemSize, this.minBufferPx, this.maxBufferPx,
this.checkAndUpdateItemSize);
}
checkAndUpdateItemSize = () => {
const sampleItem = document.querySelector('cdk-virtual-scroll-viewport .virtual-scroll-item') as HTMLElement;
const newItemSize = sampleItem?.offsetHeight;
if (newItemSize != null && newItemSize !== this.itemSize) {
this.itemSize = newItemSize;
this._scrollStrategy.updateItemAndBufferSize(this.itemSize, this.minBufferPx, this.maxBufferPx);
}
constructor() {
super();
this._scrollStrategy = new CipherListVirtualScrollStrategy(
this.itemSize,
this.minBufferPx,
this.maxBufferPx,
this.checkAndUpdateItemSize
);
}
checkAndUpdateItemSize = () => {
const sampleItem = document.querySelector(
"cdk-virtual-scroll-viewport .virtual-scroll-item"
) as HTMLElement;
const newItemSize = sampleItem?.offsetHeight;
if (newItemSize != null && newItemSize !== this.itemSize) {
this.itemSize = newItemSize;
this._scrollStrategy.updateItemAndBufferSize(
this.itemSize,
this.minBufferPx,
this.maxBufferPx
);
}
};
}

View File

@@ -1,20 +1,14 @@
import {
Directive,
ElementRef,
HostListener,
Input,
} from '@angular/core';
import { Directive, ElementRef, HostListener, Input } from "@angular/core";
@Directive({
selector: '[appFallbackSrc]',
selector: "[appFallbackSrc]",
})
export class FallbackSrcDirective {
@Input('appFallbackSrc') appFallbackSrc: string;
@Input("appFallbackSrc") appFallbackSrc: string;
constructor(private el: ElementRef) {
}
constructor(private el: ElementRef) {}
@HostListener('error') onError() {
this.el.nativeElement.src = this.appFallbackSrc;
}
@HostListener("error") onError() {
this.el.nativeElement.src = this.appFallbackSrc;
}
}

View File

@@ -1,37 +1,32 @@
import {
Directive,
ElementRef,
Input,
Renderer2,
} from '@angular/core';
import { Directive, ElementRef, Input, Renderer2 } from "@angular/core";
@Directive({
selector: '[appInputVerbatim]',
selector: "[appInputVerbatim]",
})
export class InputVerbatimDirective {
@Input() set appInputVerbatim(condition: boolean | string) {
this.disableComplete = condition === '' || condition === true;
@Input() set appInputVerbatim(condition: boolean | string) {
this.disableComplete = condition === "" || condition === true;
}
private disableComplete: boolean;
constructor(private el: ElementRef, private renderer: Renderer2) {}
ngOnInit() {
if (this.disableComplete && !this.el.nativeElement.hasAttribute("autocomplete")) {
this.renderer.setAttribute(this.el.nativeElement, "autocomplete", "off");
}
private disableComplete: boolean;
constructor(private el: ElementRef, private renderer: Renderer2) { }
ngOnInit() {
if (this.disableComplete && !this.el.nativeElement.hasAttribute('autocomplete')) {
this.renderer.setAttribute(this.el.nativeElement, 'autocomplete', 'off');
}
if (!this.el.nativeElement.hasAttribute('autocapitalize')) {
this.renderer.setAttribute(this.el.nativeElement, 'autocapitalize', 'none');
}
if (!this.el.nativeElement.hasAttribute('autocorrect')) {
this.renderer.setAttribute(this.el.nativeElement, 'autocorrect', 'none');
}
if (!this.el.nativeElement.hasAttribute('spellcheck')) {
this.renderer.setAttribute(this.el.nativeElement, 'spellcheck', 'false');
}
if (!this.el.nativeElement.hasAttribute('inputmode')) {
this.renderer.setAttribute(this.el.nativeElement, 'inputmode', 'verbatim');
}
if (!this.el.nativeElement.hasAttribute("autocapitalize")) {
this.renderer.setAttribute(this.el.nativeElement, "autocapitalize", "none");
}
if (!this.el.nativeElement.hasAttribute("autocorrect")) {
this.renderer.setAttribute(this.el.nativeElement, "autocorrect", "none");
}
if (!this.el.nativeElement.hasAttribute("spellcheck")) {
this.renderer.setAttribute(this.el.nativeElement, "spellcheck", "false");
}
if (!this.el.nativeElement.hasAttribute("inputmode")) {
this.renderer.setAttribute(this.el.nativeElement, "inputmode", "verbatim");
}
}
}

View File

@@ -1,41 +1,37 @@
import {
Directive,
ElementRef,
HostListener,
} from '@angular/core';
import { Directive, ElementRef, HostListener } from "@angular/core";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
@Directive({
selector: '[appSelectCopy]',
selector: "[appSelectCopy]",
})
export class SelectCopyDirective {
constructor(private el: ElementRef, private platformUtilsService: PlatformUtilsService) { }
constructor(private el: ElementRef, private platformUtilsService: PlatformUtilsService) {}
@HostListener('copy') onCopy() {
if (window == null) {
return;
}
let copyText = '';
const selection = window.getSelection();
for (let i = 0; i < selection.rangeCount; i++) {
const range = selection.getRangeAt(i);
const text = range.toString();
// The selection should only contain one line of text. In some cases however, the
// selection contains newlines and space characters from the indentation of following
// sibling nodes. To avoid copying passwords containing trailing newlines and spaces
// that aren't part of the password, the selection has to be trimmed.
let stringEndPos = text.length;
const newLinePos = text.search(/(?:\r\n|\r|\n)/);
if (newLinePos > -1) {
const otherPart = text.substr(newLinePos).trim();
if (otherPart === '') {
stringEndPos = newLinePos;
}
}
copyText += text.substring(0, stringEndPos);
}
this.platformUtilsService.copyToClipboard(copyText, { window: window });
@HostListener("copy") onCopy() {
if (window == null) {
return;
}
let copyText = "";
const selection = window.getSelection();
for (let i = 0; i < selection.rangeCount; i++) {
const range = selection.getRangeAt(i);
const text = range.toString();
// The selection should only contain one line of text. In some cases however, the
// selection contains newlines and space characters from the indentation of following
// sibling nodes. To avoid copying passwords containing trailing newlines and spaces
// that aren't part of the password, the selection has to be trimmed.
let stringEndPos = text.length;
const newLinePos = text.search(/(?:\r\n|\r|\n)/);
if (newLinePos > -1) {
const otherPart = text.substr(newLinePos).trim();
if (otherPart === "") {
stringEndPos = newLinePos;
}
}
copyText += text.substring(0, stringEndPos);
}
this.platformUtilsService.copyToClipboard(copyText, { window: window });
}
}

View File

@@ -1,13 +1,10 @@
import {
Directive,
HostListener,
} from '@angular/core';
import { Directive, HostListener } from "@angular/core";
@Directive({
selector: '[appStopClick]',
selector: "[appStopClick]",
})
export class StopClickDirective {
@HostListener('click', ['$event']) onClick($event: MouseEvent) {
$event.preventDefault();
}
@HostListener("click", ["$event"]) onClick($event: MouseEvent) {
$event.preventDefault();
}
}

View File

@@ -1,13 +1,10 @@
import {
Directive,
HostListener,
} from '@angular/core';
import { Directive, HostListener } from "@angular/core";
@Directive({
selector: '[appStopProp]',
selector: "[appStopProp]",
})
export class StopPropDirective {
@HostListener('click', ['$event']) onClick($event: MouseEvent) {
$event.stopPropagation();
}
@HostListener("click", ["$event"]) onClick($event: MouseEvent) {
$event.stopPropagation();
}
}

View File

@@ -1,54 +1,49 @@
import {
Directive,
ElementRef,
forwardRef,
HostListener,
Input,
Renderer2,
} from '@angular/core';
import {
ControlValueAccessor,
NgControl,
NG_VALUE_ACCESSOR,
} from '@angular/forms';
import { Directive, ElementRef, forwardRef, HostListener, Input, Renderer2 } from "@angular/core";
import { ControlValueAccessor, NgControl, NG_VALUE_ACCESSOR } from "@angular/forms";
// ref: https://juristr.com/blog/2018/02/ng-true-value-directive/
@Directive({
selector: 'input[type=checkbox][appTrueFalseValue]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TrueFalseValueDirective),
multi: true,
},
],
selector: "input[type=checkbox][appTrueFalseValue]",
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TrueFalseValueDirective),
multi: true,
},
],
})
export class TrueFalseValueDirective implements ControlValueAccessor {
@Input() trueValue = true;
@Input() falseValue = false;
@Input() trueValue = true;
@Input() falseValue = false;
constructor(private elementRef: ElementRef, private renderer: Renderer2) { }
constructor(private elementRef: ElementRef, private renderer: Renderer2) {}
@HostListener('change', ['$event'])
onHostChange(ev: any) {
this.propagateChange(ev.target.checked ? this.trueValue : this.falseValue);
@HostListener("change", ["$event"])
onHostChange(ev: any) {
this.propagateChange(ev.target.checked ? this.trueValue : this.falseValue);
}
writeValue(obj: any): void {
if (obj === this.trueValue) {
this.renderer.setProperty(this.elementRef.nativeElement, "checked", true);
} else {
this.renderer.setProperty(this.elementRef.nativeElement, "checked", false);
}
}
writeValue(obj: any): void {
if (obj === this.trueValue) {
this.renderer.setProperty(this.elementRef.nativeElement, 'checked', true);
} else {
this.renderer.setProperty(this.elementRef.nativeElement, 'checked', false);
}
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
/* nothing */
}
registerOnTouched(fn: any): void { /* nothing */ }
setDisabledState?(isDisabled: boolean): void {
/* nothing */
}
setDisabledState?(isDisabled: boolean): void { /* nothing */ }
private propagateChange = (_: any) => { /* nothing */ };
private propagateChange = (_: any) => {
/* nothing */
};
}

View File

@@ -1,53 +1,50 @@
import {
Pipe,
PipeTransform,
} from '@angular/core';
import { Utils } from 'jslib-common/misc/utils';
import { Pipe, PipeTransform } from "@angular/core";
import { Utils } from "jslib-common/misc/utils";
/*
An updated pipe that sanitizes HTML, highlights numbers and special characters (in different colors each)
and handles Unicode / Emoji characters correctly.
*/
@Pipe({ name: 'colorPassword' })
@Pipe({ name: "colorPassword" })
export class ColorPasswordPipe implements PipeTransform {
transform(password: string) {
// Convert to an array to handle cases that stings have special characters, ie: emoji.
const passwordArray = Array.from(password);
let colorizedPassword = '';
for (let i = 0; i < passwordArray.length; i++) {
let character = passwordArray[i];
let isSpecial = false;
// Sanitize HTML first.
switch (character) {
case '&':
character = '&amp;';
isSpecial = true;
break;
case '<':
character = '&lt;';
isSpecial = true;
break;
case '>':
character = '&gt;';
isSpecial = true;
break;
case ' ':
character = '&nbsp;';
isSpecial = true;
break;
default:
break;
}
let type = 'letter';
if (character.match(Utils.regexpEmojiPresentation)) {
type = 'emoji';
} else if (isSpecial || character.match(/[^\w ]/)) {
type = 'special';
} else if (character.match(/\d/)) {
type = 'number';
}
colorizedPassword += '<span class="password-' + type + '">' + character + '</span>';
}
return colorizedPassword;
transform(password: string) {
// Convert to an array to handle cases that stings have special characters, ie: emoji.
const passwordArray = Array.from(password);
let colorizedPassword = "";
for (let i = 0; i < passwordArray.length; i++) {
let character = passwordArray[i];
let isSpecial = false;
// Sanitize HTML first.
switch (character) {
case "&":
character = "&amp;";
isSpecial = true;
break;
case "<":
character = "&lt;";
isSpecial = true;
break;
case ">":
character = "&gt;";
isSpecial = true;
break;
case " ":
character = "&nbsp;";
isSpecial = true;
break;
default:
break;
}
let type = "letter";
if (character.match(Utils.regexpEmojiPresentation)) {
type = "emoji";
} else if (isSpecial || character.match(/[^\w ]/)) {
type = "special";
} else if (character.match(/\d/)) {
type = "number";
}
colorizedPassword += '<span class="password-' + type + '">' + character + "</span>";
}
return colorizedPassword;
}
}

View File

@@ -1,17 +1,14 @@
import {
Pipe,
PipeTransform,
} from '@angular/core';
import { Pipe, PipeTransform } from "@angular/core";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
@Pipe({
name: 'i18n',
name: "i18n",
})
export class I18nPipe implements PipeTransform {
constructor(private i18nService: I18nService) { }
constructor(private i18nService: I18nService) {}
transform(id: string, p1?: string, p2?: string, p3?: string): string {
return this.i18nService.t(id, p1, p2, p3);
}
transform(id: string, p1?: string, p2?: string, p3?: string): string {
return this.i18nService.t(id, p1, p2, p3);
}
}

View File

@@ -1,44 +1,41 @@
import {
Pipe,
PipeTransform,
} from '@angular/core';
import { Pipe, PipeTransform } from "@angular/core";
import { CipherView } from 'jslib-common/models/view/cipherView';
import { CipherView } from "jslib-common/models/view/cipherView";
@Pipe({
name: 'searchCiphers',
name: "searchCiphers",
})
export class SearchCiphersPipe implements PipeTransform {
transform(ciphers: CipherView[], searchText: string, deleted: boolean = false): CipherView[] {
if (ciphers == null || ciphers.length === 0) {
return [];
}
if (searchText == null || searchText.length < 2) {
return ciphers.filter(c => {
return deleted !== c.isDeleted;
});
}
searchText = searchText.trim().toLowerCase();
return ciphers.filter(c => {
if (deleted !== c.isDeleted) {
return false;
}
if (c.name != null && c.name.toLowerCase().indexOf(searchText) > -1) {
return true;
}
if (searchText.length >= 8 && c.id.startsWith(searchText)) {
return true;
}
if (c.subTitle != null && c.subTitle.toLowerCase().indexOf(searchText) > -1) {
return true;
}
if (c.login && c.login.uri != null && c.login.uri.toLowerCase().indexOf(searchText) > -1) {
return true;
}
return false;
});
transform(ciphers: CipherView[], searchText: string, deleted: boolean = false): CipherView[] {
if (ciphers == null || ciphers.length === 0) {
return [];
}
if (searchText == null || searchText.length < 2) {
return ciphers.filter((c) => {
return deleted !== c.isDeleted;
});
}
searchText = searchText.trim().toLowerCase();
return ciphers.filter((c) => {
if (deleted !== c.isDeleted) {
return false;
}
if (c.name != null && c.name.toLowerCase().indexOf(searchText) > -1) {
return true;
}
if (searchText.length >= 8 && c.id.startsWith(searchText)) {
return true;
}
if (c.subTitle != null && c.subTitle.toLowerCase().indexOf(searchText) > -1) {
return true;
}
if (c.login && c.login.uri != null && c.login.uri.toLowerCase().indexOf(searchText) > -1) {
return true;
}
return false;
});
}
}

View File

@@ -1,33 +1,48 @@
import {
Pipe,
PipeTransform,
} from '@angular/core';
import { Pipe, PipeTransform } from "@angular/core";
@Pipe({
name: 'search',
name: "search",
})
export class SearchPipe implements PipeTransform {
transform(items: any[], searchText: string, prop1?: string, prop2?: string, prop3?: string): any[] {
if (items == null || items.length === 0) {
return [];
}
if (searchText == null || searchText.length < 2) {
return items;
}
searchText = searchText.trim().toLowerCase();
return items.filter(i => {
if (prop1 != null && i[prop1] != null && i[prop1].toString().toLowerCase().indexOf(searchText) > -1) {
return true;
}
if (prop2 != null && i[prop2] != null && i[prop2].toString().toLowerCase().indexOf(searchText) > -1) {
return true;
}
if (prop3 != null && i[prop3] != null && i[prop3].toString().toLowerCase().indexOf(searchText) > -1) {
return true;
}
return false;
});
transform(
items: any[],
searchText: string,
prop1?: string,
prop2?: string,
prop3?: string
): any[] {
if (items == null || items.length === 0) {
return [];
}
if (searchText == null || searchText.length < 2) {
return items;
}
searchText = searchText.trim().toLowerCase();
return items.filter((i) => {
if (
prop1 != null &&
i[prop1] != null &&
i[prop1].toString().toLowerCase().indexOf(searchText) > -1
) {
return true;
}
if (
prop2 != null &&
i[prop2] != null &&
i[prop2].toString().toLowerCase().indexOf(searchText) > -1
) {
return true;
}
if (
prop3 != null &&
i[prop3] != null &&
i[prop3].toString().toLowerCase().indexOf(searchText) > -1
) {
return true;
}
return false;
});
}
}

View File

@@ -1,22 +1,19 @@
import {
Pipe,
PipeTransform,
} from '@angular/core';
import { Pipe, PipeTransform } from "@angular/core";
interface User {
name?: string;
email: string;
name?: string;
email: string;
}
@Pipe({
name: 'userName',
name: "userName",
})
export class UserNamePipe implements PipeTransform {
transform(user?: User): string {
if (user == null) {
return null;
}
return user.name == null || user.name.trim() === '' ? user.email : user.name;
transform(user?: User): string {
if (user == null) {
return null;
}
return user.name == null || user.name.trim() === "" ? user.email : user.name;
}
}

View File

@@ -1,90 +1,89 @@
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-display: auto;
src: url(webfonts/Open_Sans-italic-300.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: italic;
font-weight: 300;
font-display: auto;
src: url(webfonts/Open_Sans-italic-300.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-display: auto;
src: url(webfonts/Open_Sans-italic-400.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: italic;
font-weight: 400;
font-display: auto;
src: url(webfonts/Open_Sans-italic-400.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-display: auto;
src: url(webfonts/Open_Sans-italic-600.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: italic;
font-weight: 600;
font-display: auto;
src: url(webfonts/Open_Sans-italic-600.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
font-display: auto;
src: url(webfonts/Open_Sans-italic-700.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: italic;
font-weight: 700;
font-display: auto;
src: url(webfonts/Open_Sans-italic-700.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 800;
font-display: auto;
src: url(webfonts/Open_Sans-italic-800.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: italic;
font-weight: 800;
font-display: auto;
src: url(webfonts/Open_Sans-italic-800.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-display: auto;
src: url(webfonts/Open_Sans-normal-300.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: normal;
font-weight: 300;
font-display: auto;
src: url(webfonts/Open_Sans-normal-300.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-display: auto;
src: url(webfonts/Open_Sans-normal-400.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: normal;
font-weight: 400;
font-display: auto;
src: url(webfonts/Open_Sans-normal-400.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-display: auto;
src: url(webfonts/Open_Sans-normal-600.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: normal;
font-weight: 600;
font-display: auto;
src: url(webfonts/Open_Sans-normal-600.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-display: auto;
src: url(webfonts/Open_Sans-normal-700.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: normal;
font-weight: 700;
font-display: auto;
src: url(webfonts/Open_Sans-normal-700.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 800;
font-display: auto;
src: url(webfonts/Open_Sans-normal-800.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: normal;
font-weight: 800;
font-display: auto;
src: url(webfonts/Open_Sans-normal-800.woff) format("woff");
unicode-range: U+0-10FFFF;
}

View File

@@ -1,43 +1,45 @@
import { Injectable } from '@angular/core';
import {
ActivatedRouteSnapshot,
CanActivate,
Router,
RouterStateSnapshot,
} from '@angular/router';
import { Injectable } from "@angular/core";
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from "@angular/router";
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
@Injectable()
export class AuthGuardService implements CanActivate {
constructor(private vaultTimeoutService: VaultTimeoutService, private router: Router,
private messagingService: MessagingService, private keyConnectorService: KeyConnectorService,
private stateService: StateService) { }
constructor(
private vaultTimeoutService: VaultTimeoutService,
private router: Router,
private messagingService: MessagingService,
private keyConnectorService: KeyConnectorService,
private stateService: StateService
) {}
async canActivate(route: ActivatedRouteSnapshot, routerState: RouterStateSnapshot) {
const isAuthed = await this.stateService.getIsAuthenticated();
if (!isAuthed) {
this.messagingService.send('authBlocked');
return false;
}
const locked = await this.vaultTimeoutService.isLocked();
if (locked) {
if (routerState != null) {
this.messagingService.send('lockedUrl', { url: routerState.url });
}
this.router.navigate(['lock'], { queryParams: { promptBiometric: true }});
return false;
}
if (!routerState.url.includes('remove-password') && await this.keyConnectorService.getConvertAccountRequired()) {
this.router.navigate(['/remove-password']);
return false;
}
return true;
async canActivate(route: ActivatedRouteSnapshot, routerState: RouterStateSnapshot) {
const isAuthed = await this.stateService.getIsAuthenticated();
if (!isAuthed) {
this.messagingService.send("authBlocked");
return false;
}
const locked = await this.vaultTimeoutService.isLocked();
if (locked) {
if (routerState != null) {
this.messagingService.send("lockedUrl", { url: routerState.url });
}
this.router.navigate(["lock"], { queryParams: { promptBiometric: true } });
return false;
}
if (
!routerState.url.includes("remove-password") &&
(await this.keyConnectorService.getConvertAccountRequired())
) {
this.router.navigate(["/remove-password"]);
return false;
}
return true;
}
}

View File

@@ -1,7 +1,6 @@
import { Injectable } from '@angular/core';
import { Injectable } from "@angular/core";
import { BroadcasterService as BaseBroadcasterService } from 'jslib-common/services/broadcaster.service';
import { BroadcasterService as BaseBroadcasterService } from "jslib-common/services/broadcaster.service";
@Injectable()
export class BroadcasterService extends BaseBroadcasterService {
}
export class BroadcasterService extends BaseBroadcasterService {}

View File

@@ -1,480 +1,452 @@
import {
Injector,
LOCALE_ID,
NgModule,
} from '@angular/core';
import { Injector, LOCALE_ID, NgModule } from "@angular/core";
import { ApiService } from 'jslib-common/services/api.service';
import { AppIdService } from 'jslib-common/services/appId.service';
import { AuditService } from 'jslib-common/services/audit.service';
import { AuthService } from 'jslib-common/services/auth.service';
import { CipherService } from 'jslib-common/services/cipher.service';
import { CollectionService } from 'jslib-common/services/collection.service';
import { ConsoleLogService } from 'jslib-common/services/consoleLog.service';
import { CryptoService } from 'jslib-common/services/crypto.service';
import { EnvironmentService } from 'jslib-common/services/environment.service';
import { EventService } from 'jslib-common/services/event.service';
import { ExportService } from 'jslib-common/services/export.service';
import { FileUploadService } from 'jslib-common/services/fileUpload.service';
import { FolderService } from 'jslib-common/services/folder.service';
import { KeyConnectorService } from 'jslib-common/services/keyConnector.service';
import { NotificationsService } from 'jslib-common/services/notifications.service';
import { OrganizationService } from 'jslib-common/services/organization.service';
import { PasswordGenerationService } from 'jslib-common/services/passwordGeneration.service';
import { PolicyService } from 'jslib-common/services/policy.service';
import { ProviderService } from 'jslib-common/services/provider.service';
import { SearchService } from 'jslib-common/services/search.service';
import { SendService } from 'jslib-common/services/send.service';
import { SettingsService } from 'jslib-common/services/settings.service';
import { StateService } from 'jslib-common/services/state.service';
import { StateMigrationService } from 'jslib-common/services/stateMigration.service';
import { SyncService } from 'jslib-common/services/sync.service';
import { TokenService } from 'jslib-common/services/token.service';
import { TotpService } from 'jslib-common/services/totp.service';
import { UserVerificationService } from 'jslib-common/services/userVerification.service';
import { VaultTimeoutService } from 'jslib-common/services/vaultTimeout.service';
import { WebCryptoFunctionService } from 'jslib-common/services/webCryptoFunction.service';
import { ApiService } from "jslib-common/services/api.service";
import { AppIdService } from "jslib-common/services/appId.service";
import { AuditService } from "jslib-common/services/audit.service";
import { AuthService } from "jslib-common/services/auth.service";
import { CipherService } from "jslib-common/services/cipher.service";
import { CollectionService } from "jslib-common/services/collection.service";
import { ConsoleLogService } from "jslib-common/services/consoleLog.service";
import { CryptoService } from "jslib-common/services/crypto.service";
import { EnvironmentService } from "jslib-common/services/environment.service";
import { EventService } from "jslib-common/services/event.service";
import { ExportService } from "jslib-common/services/export.service";
import { FileUploadService } from "jslib-common/services/fileUpload.service";
import { FolderService } from "jslib-common/services/folder.service";
import { KeyConnectorService } from "jslib-common/services/keyConnector.service";
import { NotificationsService } from "jslib-common/services/notifications.service";
import { OrganizationService } from "jslib-common/services/organization.service";
import { PasswordGenerationService } from "jslib-common/services/passwordGeneration.service";
import { PolicyService } from "jslib-common/services/policy.service";
import { ProviderService } from "jslib-common/services/provider.service";
import { SearchService } from "jslib-common/services/search.service";
import { SendService } from "jslib-common/services/send.service";
import { SettingsService } from "jslib-common/services/settings.service";
import { StateService } from "jslib-common/services/state.service";
import { StateMigrationService } from "jslib-common/services/stateMigration.service";
import { SyncService } from "jslib-common/services/sync.service";
import { TokenService } from "jslib-common/services/token.service";
import { TotpService } from "jslib-common/services/totp.service";
import { UserVerificationService } from "jslib-common/services/userVerification.service";
import { VaultTimeoutService } from "jslib-common/services/vaultTimeout.service";
import { WebCryptoFunctionService } from "jslib-common/services/webCryptoFunction.service";
import { ApiService as ApiServiceAbstraction } from 'jslib-common/abstractions/api.service';
import { AppIdService as AppIdServiceAbstraction } from 'jslib-common/abstractions/appId.service';
import { AuditService as AuditServiceAbstraction } from 'jslib-common/abstractions/audit.service';
import { AuthService as AuthServiceAbstraction } from 'jslib-common/abstractions/auth.service';
import { BroadcasterService as BroadcasterServiceAbstraction } from 'jslib-common/abstractions/broadcaster.service';
import { CipherService as CipherServiceAbstraction } from 'jslib-common/abstractions/cipher.service';
import { CollectionService as CollectionServiceAbstraction } from 'jslib-common/abstractions/collection.service';
import { CryptoService as CryptoServiceAbstraction } from 'jslib-common/abstractions/crypto.service';
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService as EnvironmentServiceAbstraction } from 'jslib-common/abstractions/environment.service';
import { EventService as EventServiceAbstraction } from 'jslib-common/abstractions/event.service';
import { ExportService as ExportServiceAbstraction } from 'jslib-common/abstractions/export.service';
import { FileUploadService as FileUploadServiceAbstraction } from 'jslib-common/abstractions/fileUpload.service';
import { FolderService as FolderServiceAbstraction } from 'jslib-common/abstractions/folder.service';
import { I18nService as I18nServiceAbstraction } from 'jslib-common/abstractions/i18n.service';
import { KeyConnectorService as KeyConnectorServiceAbstraction } from 'jslib-common/abstractions/keyConnector.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService as MessagingServiceAbstraction } from 'jslib-common/abstractions/messaging.service';
import { NotificationsService as NotificationsServiceAbstraction } from 'jslib-common/abstractions/notifications.service';
import { OrganizationService as OrganizationServiceAbstraction } from 'jslib-common/abstractions/organization.service';
import {
PasswordGenerationService as PasswordGenerationServiceAbstraction,
} from 'jslib-common/abstractions/passwordGeneration.service';
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from 'jslib-common/abstractions/passwordReprompt.service';
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService as PolicyServiceAbstraction } from 'jslib-common/abstractions/policy.service';
import { ProviderService as ProviderServiceAbstraction } from 'jslib-common/abstractions/provider.service';
import { SearchService as SearchServiceAbstraction } from 'jslib-common/abstractions/search.service';
import { SendService as SendServiceAbstraction } from 'jslib-common/abstractions/send.service';
import { SettingsService as SettingsServiceAbstraction } from 'jslib-common/abstractions/settings.service';
import { StateService as StateServiceAbstraction } from 'jslib-common/abstractions/state.service';
import { StateMigrationService as StateMigrationServiceAbstraction } from 'jslib-common/abstractions/stateMigration.service';
import { StorageService as StorageServiceAbstraction } from 'jslib-common/abstractions/storage.service';
import { SyncService as SyncServiceAbstraction } from 'jslib-common/abstractions/sync.service';
import { TokenService as TokenServiceAbstraction } from 'jslib-common/abstractions/token.service';
import { TotpService as TotpServiceAbstraction } from 'jslib-common/abstractions/totp.service';
import { UserVerificationService as UserVerificationServiceAbstraction } from 'jslib-common/abstractions/userVerification.service';
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from 'jslib-common/abstractions/vaultTimeout.service';
import { ApiService as ApiServiceAbstraction } from "jslib-common/abstractions/api.service";
import { AppIdService as AppIdServiceAbstraction } from "jslib-common/abstractions/appId.service";
import { AuditService as AuditServiceAbstraction } from "jslib-common/abstractions/audit.service";
import { AuthService as AuthServiceAbstraction } from "jslib-common/abstractions/auth.service";
import { BroadcasterService as BroadcasterServiceAbstraction } from "jslib-common/abstractions/broadcaster.service";
import { CipherService as CipherServiceAbstraction } from "jslib-common/abstractions/cipher.service";
import { CollectionService as CollectionServiceAbstraction } from "jslib-common/abstractions/collection.service";
import { CryptoService as CryptoServiceAbstraction } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService as EnvironmentServiceAbstraction } from "jslib-common/abstractions/environment.service";
import { EventService as EventServiceAbstraction } from "jslib-common/abstractions/event.service";
import { ExportService as ExportServiceAbstraction } from "jslib-common/abstractions/export.service";
import { FileUploadService as FileUploadServiceAbstraction } from "jslib-common/abstractions/fileUpload.service";
import { FolderService as FolderServiceAbstraction } from "jslib-common/abstractions/folder.service";
import { I18nService as I18nServiceAbstraction } from "jslib-common/abstractions/i18n.service";
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "jslib-common/abstractions/keyConnector.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService as MessagingServiceAbstraction } from "jslib-common/abstractions/messaging.service";
import { NotificationsService as NotificationsServiceAbstraction } from "jslib-common/abstractions/notifications.service";
import { OrganizationService as OrganizationServiceAbstraction } from "jslib-common/abstractions/organization.service";
import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from "jslib-common/abstractions/passwordGeneration.service";
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService as PolicyServiceAbstraction } from "jslib-common/abstractions/policy.service";
import { ProviderService as ProviderServiceAbstraction } from "jslib-common/abstractions/provider.service";
import { SearchService as SearchServiceAbstraction } from "jslib-common/abstractions/search.service";
import { SendService as SendServiceAbstraction } from "jslib-common/abstractions/send.service";
import { SettingsService as SettingsServiceAbstraction } from "jslib-common/abstractions/settings.service";
import { StateService as StateServiceAbstraction } from "jslib-common/abstractions/state.service";
import { StateMigrationService as StateMigrationServiceAbstraction } from "jslib-common/abstractions/stateMigration.service";
import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service";
import { SyncService as SyncServiceAbstraction } from "jslib-common/abstractions/sync.service";
import { TokenService as TokenServiceAbstraction } from "jslib-common/abstractions/token.service";
import { TotpService as TotpServiceAbstraction } from "jslib-common/abstractions/totp.service";
import { UserVerificationService as UserVerificationServiceAbstraction } from "jslib-common/abstractions/userVerification.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "jslib-common/abstractions/vaultTimeout.service";
import { AuthGuardService } from './auth-guard.service';
import { BroadcasterService } from './broadcaster.service';
import { LockGuardService } from './lock-guard.service';
import { ModalService } from './modal.service';
import { PasswordRepromptService } from './passwordReprompt.service';
import { UnauthGuardService } from './unauth-guard.service';
import { ValidationService } from './validation.service';
import { AuthGuardService } from "./auth-guard.service";
import { BroadcasterService } from "./broadcaster.service";
import { LockGuardService } from "./lock-guard.service";
import { ModalService } from "./modal.service";
import { PasswordRepromptService } from "./passwordReprompt.service";
import { UnauthGuardService } from "./unauth-guard.service";
import { ValidationService } from "./validation.service";
@NgModule({
declarations: [],
providers: [
{ provide: 'WINDOW', useValue: window },
{
provide: LOCALE_ID,
useFactory: (i18nService: I18nServiceAbstraction) => i18nService.translationLocale,
deps: [I18nServiceAbstraction],
},
ValidationService,
AuthGuardService,
UnauthGuardService,
LockGuardService,
ModalService,
{
provide: AppIdServiceAbstraction,
useClass: AppIdService,
deps: [StorageServiceAbstraction],
},
{
provide: AuditServiceAbstraction,
useClass: AuditService,
deps: [CryptoFunctionServiceAbstraction, ApiServiceAbstraction],
},
{
provide: AuthServiceAbstraction,
useClass: AuthService,
deps: [
CryptoServiceAbstraction,
ApiServiceAbstraction,
TokenServiceAbstraction,
AppIdServiceAbstraction,
I18nServiceAbstraction,
PlatformUtilsServiceAbstraction,
MessagingServiceAbstraction,
VaultTimeoutServiceAbstraction,
LogService,
CryptoFunctionServiceAbstraction,
KeyConnectorServiceAbstraction,
EnvironmentServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: CipherServiceAbstraction,
useFactory: (
cryptoService: CryptoServiceAbstraction,
settingsService: SettingsServiceAbstraction,
apiService: ApiServiceAbstraction,
fileUploadService: FileUploadServiceAbstraction,
i18nService: I18nServiceAbstraction,
injector: Injector,
logService: LogService,
stateService: StateServiceAbstraction,
) => new CipherService(
cryptoService,
settingsService,
apiService,
fileUploadService,
i18nService,
() => injector.get(SearchServiceAbstraction),
logService,
stateService,
),
deps: [
CryptoServiceAbstraction,
SettingsServiceAbstraction,
ApiServiceAbstraction,
FileUploadServiceAbstraction,
I18nServiceAbstraction,
Injector, // TODO: Get rid of this circular dependency!
LogService,
StateServiceAbstraction,
],
},
{
provide: FolderServiceAbstraction,
useClass: FolderService,
deps: [
CryptoServiceAbstraction,
ApiServiceAbstraction,
I18nServiceAbstraction,
CipherServiceAbstraction,
StateServiceAbstraction,
],
},
{ provide: LogService, useFactory: () => new ConsoleLogService(false) },
{
provide: CollectionServiceAbstraction,
useClass: CollectionService,
deps: [
CryptoServiceAbstraction,
I18nServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: EnvironmentServiceAbstraction,
useClass: EnvironmentService,
deps: [StateServiceAbstraction],
},
{
provide: TotpServiceAbstraction,
useClass: TotpService,
deps: [
CryptoFunctionServiceAbstraction,
LogService,
StateServiceAbstraction,
],
},
{ provide: TokenServiceAbstraction, useClass: TokenService, deps: [StateServiceAbstraction] },
{
provide: CryptoServiceAbstraction,
useClass: CryptoService,
deps: [
CryptoFunctionServiceAbstraction,
PlatformUtilsServiceAbstraction,
LogService,
StateServiceAbstraction,
],
},
{
provide: PasswordGenerationServiceAbstraction,
useClass: PasswordGenerationService,
deps: [
CryptoServiceAbstraction,
PolicyServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: ApiServiceAbstraction,
useFactory: (tokenService: TokenServiceAbstraction, platformUtilsService: PlatformUtilsServiceAbstraction,
environmentService: EnvironmentServiceAbstraction, messagingService: MessagingServiceAbstraction) =>
new ApiService(tokenService, platformUtilsService, environmentService,
async (expired: boolean) => messagingService.send('logout', { expired: expired })),
deps: [
TokenServiceAbstraction,
PlatformUtilsServiceAbstraction,
EnvironmentServiceAbstraction,
MessagingServiceAbstraction,
],
},
{
provide: FileUploadServiceAbstraction,
useClass: FileUploadService,
deps: [
LogService,
ApiServiceAbstraction,
],
},
{
provide: SyncServiceAbstraction,
useFactory: (
apiService: ApiServiceAbstraction,
settingsService: SettingsServiceAbstraction,
folderService: FolderServiceAbstraction,
cipherService: CipherServiceAbstraction,
cryptoService: CryptoServiceAbstraction,
collectionService: CollectionServiceAbstraction,
messagingService: MessagingServiceAbstraction,
policyService: PolicyServiceAbstraction,
sendService: SendServiceAbstraction,
logService: LogService,
keyConnectorService: KeyConnectorServiceAbstraction,
stateService: StateServiceAbstraction,
organizationService: OrganizationServiceAbstraction,
providerService: ProviderServiceAbstraction,
) => new SyncService(
apiService,
settingsService,
folderService,
cipherService,
cryptoService,
collectionService,
messagingService,
policyService,
sendService,
logService,
keyConnectorService,
stateService,
organizationService,
providerService,
async (expired: boolean) => messagingService.send('logout', { expired: expired })),
deps: [
ApiServiceAbstraction,
SettingsServiceAbstraction,
FolderServiceAbstraction,
CipherServiceAbstraction,
CryptoServiceAbstraction,
CollectionServiceAbstraction,
MessagingServiceAbstraction,
PolicyServiceAbstraction,
SendServiceAbstraction,
LogService,
KeyConnectorServiceAbstraction,
StateServiceAbstraction,
OrganizationServiceAbstraction,
ProviderServiceAbstraction,
],
},
{ provide: BroadcasterServiceAbstraction, useClass: BroadcasterService },
{
provide: SettingsServiceAbstraction,
useClass: SettingsService,
deps: [StateServiceAbstraction],
},
{
provide: VaultTimeoutServiceAbstraction,
useFactory: (
cipherService: CipherServiceAbstraction,
folderService: FolderServiceAbstraction,
collectionService: CollectionServiceAbstraction,
cryptoService: CryptoServiceAbstraction,
platformUtilsService: PlatformUtilsServiceAbstraction,
messagingService: MessagingServiceAbstraction,
searchService: SearchServiceAbstraction,
tokenService: TokenServiceAbstraction,
policyService: PolicyServiceAbstraction,
keyConnectorService: KeyConnectorServiceAbstraction,
stateService: StateServiceAbstraction,
) => new VaultTimeoutService(
cipherService,
folderService,
collectionService,
cryptoService,
platformUtilsService,
messagingService,
searchService,
tokenService,
policyService,
keyConnectorService,
stateService,
null,
async () => messagingService.send('logout', { expired: false }),
),
deps: [
CipherServiceAbstraction,
FolderServiceAbstraction,
CollectionServiceAbstraction,
CryptoServiceAbstraction,
PlatformUtilsServiceAbstraction,
MessagingServiceAbstraction,
SearchServiceAbstraction,
TokenServiceAbstraction,
PolicyServiceAbstraction,
KeyConnectorServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: StateServiceAbstraction,
useClass: StateService,
deps: [
StorageServiceAbstraction,
'SECURE_STORAGE',
LogService,
StateMigrationServiceAbstraction,
],
},
{
provide: StateMigrationServiceAbstraction,
useClass: StateMigrationService,
deps: [
StorageServiceAbstraction,
'SECURE_STORAGE',
],
},
{
provide: ExportServiceAbstraction,
useClass: ExportService,
deps: [
FolderServiceAbstraction,
CipherServiceAbstraction,
ApiServiceAbstraction,
CryptoServiceAbstraction,
],
},
{
provide: SearchServiceAbstraction,
useClass: SearchService,
deps: [
CipherServiceAbstraction,
LogService,
I18nServiceAbstraction,
],
},
{
provide: NotificationsServiceAbstraction,
useFactory: (
syncService: SyncServiceAbstraction,
appIdService: AppIdServiceAbstraction,
apiService: ApiServiceAbstraction,
vaultTimeoutService: VaultTimeoutServiceAbstraction,
environmentService: EnvironmentServiceAbstraction,
messagingService: MessagingServiceAbstraction,
logService: LogService,
stateService: StateServiceAbstraction,
) => new NotificationsService(
syncService,
appIdService,
apiService,
vaultTimeoutService,
environmentService,
async () => messagingService.send('logout', { expired: true }),
logService,
stateService,
),
deps: [
SyncServiceAbstraction,
AppIdServiceAbstraction,
ApiServiceAbstraction,
VaultTimeoutServiceAbstraction,
EnvironmentServiceAbstraction,
MessagingServiceAbstraction,
LogService,
StateServiceAbstraction,
],
},
{
provide: CryptoFunctionServiceAbstraction,
useClass: WebCryptoFunctionService,
deps: ['WINDOW', PlatformUtilsServiceAbstraction],
},
{
provide: EventServiceAbstraction,
useClass: EventService,
deps: [
ApiServiceAbstraction,
CipherServiceAbstraction,
StateServiceAbstraction,
LogService,
OrganizationServiceAbstraction,
],
},
{
provide: PolicyServiceAbstraction,
useClass: PolicyService,
deps: [
StateServiceAbstraction,
OrganizationServiceAbstraction,
ApiServiceAbstraction,
],
},
{
provide: SendServiceAbstraction,
useClass: SendService,
deps: [
CryptoServiceAbstraction,
ApiServiceAbstraction,
FileUploadServiceAbstraction,
I18nServiceAbstraction,
CryptoFunctionServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: KeyConnectorServiceAbstraction,
useClass: KeyConnectorService,
deps: [
StateServiceAbstraction,
CryptoServiceAbstraction,
ApiServiceAbstraction,
TokenServiceAbstraction,
LogService,
OrganizationServiceAbstraction,
],
},
{
provide: UserVerificationServiceAbstraction,
useClass: UserVerificationService,
deps: [
CryptoServiceAbstraction,
I18nServiceAbstraction,
ApiServiceAbstraction,
],
},
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
{
provide: OrganizationServiceAbstraction,
useClass: OrganizationService,
deps: [
StateServiceAbstraction,
],
},
{
provide: ProviderServiceAbstraction,
useClass: ProviderService,
deps: [
StateServiceAbstraction,
],
},
],
declarations: [],
providers: [
{ provide: "WINDOW", useValue: window },
{
provide: LOCALE_ID,
useFactory: (i18nService: I18nServiceAbstraction) => i18nService.translationLocale,
deps: [I18nServiceAbstraction],
},
ValidationService,
AuthGuardService,
UnauthGuardService,
LockGuardService,
ModalService,
{
provide: AppIdServiceAbstraction,
useClass: AppIdService,
deps: [StorageServiceAbstraction],
},
{
provide: AuditServiceAbstraction,
useClass: AuditService,
deps: [CryptoFunctionServiceAbstraction, ApiServiceAbstraction],
},
{
provide: AuthServiceAbstraction,
useClass: AuthService,
deps: [
CryptoServiceAbstraction,
ApiServiceAbstraction,
TokenServiceAbstraction,
AppIdServiceAbstraction,
I18nServiceAbstraction,
PlatformUtilsServiceAbstraction,
MessagingServiceAbstraction,
VaultTimeoutServiceAbstraction,
LogService,
CryptoFunctionServiceAbstraction,
KeyConnectorServiceAbstraction,
EnvironmentServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: CipherServiceAbstraction,
useFactory: (
cryptoService: CryptoServiceAbstraction,
settingsService: SettingsServiceAbstraction,
apiService: ApiServiceAbstraction,
fileUploadService: FileUploadServiceAbstraction,
i18nService: I18nServiceAbstraction,
injector: Injector,
logService: LogService,
stateService: StateServiceAbstraction
) =>
new CipherService(
cryptoService,
settingsService,
apiService,
fileUploadService,
i18nService,
() => injector.get(SearchServiceAbstraction),
logService,
stateService
),
deps: [
CryptoServiceAbstraction,
SettingsServiceAbstraction,
ApiServiceAbstraction,
FileUploadServiceAbstraction,
I18nServiceAbstraction,
Injector, // TODO: Get rid of this circular dependency!
LogService,
StateServiceAbstraction,
],
},
{
provide: FolderServiceAbstraction,
useClass: FolderService,
deps: [
CryptoServiceAbstraction,
ApiServiceAbstraction,
I18nServiceAbstraction,
CipherServiceAbstraction,
StateServiceAbstraction,
],
},
{ provide: LogService, useFactory: () => new ConsoleLogService(false) },
{
provide: CollectionServiceAbstraction,
useClass: CollectionService,
deps: [CryptoServiceAbstraction, I18nServiceAbstraction, StateServiceAbstraction],
},
{
provide: EnvironmentServiceAbstraction,
useClass: EnvironmentService,
deps: [StateServiceAbstraction],
},
{
provide: TotpServiceAbstraction,
useClass: TotpService,
deps: [CryptoFunctionServiceAbstraction, LogService, StateServiceAbstraction],
},
{ provide: TokenServiceAbstraction, useClass: TokenService, deps: [StateServiceAbstraction] },
{
provide: CryptoServiceAbstraction,
useClass: CryptoService,
deps: [
CryptoFunctionServiceAbstraction,
PlatformUtilsServiceAbstraction,
LogService,
StateServiceAbstraction,
],
},
{
provide: PasswordGenerationServiceAbstraction,
useClass: PasswordGenerationService,
deps: [CryptoServiceAbstraction, PolicyServiceAbstraction, StateServiceAbstraction],
},
{
provide: ApiServiceAbstraction,
useFactory: (
tokenService: TokenServiceAbstraction,
platformUtilsService: PlatformUtilsServiceAbstraction,
environmentService: EnvironmentServiceAbstraction,
messagingService: MessagingServiceAbstraction
) =>
new ApiService(
tokenService,
platformUtilsService,
environmentService,
async (expired: boolean) => messagingService.send("logout", { expired: expired })
),
deps: [
TokenServiceAbstraction,
PlatformUtilsServiceAbstraction,
EnvironmentServiceAbstraction,
MessagingServiceAbstraction,
],
},
{
provide: FileUploadServiceAbstraction,
useClass: FileUploadService,
deps: [LogService, ApiServiceAbstraction],
},
{
provide: SyncServiceAbstraction,
useFactory: (
apiService: ApiServiceAbstraction,
settingsService: SettingsServiceAbstraction,
folderService: FolderServiceAbstraction,
cipherService: CipherServiceAbstraction,
cryptoService: CryptoServiceAbstraction,
collectionService: CollectionServiceAbstraction,
messagingService: MessagingServiceAbstraction,
policyService: PolicyServiceAbstraction,
sendService: SendServiceAbstraction,
logService: LogService,
keyConnectorService: KeyConnectorServiceAbstraction,
stateService: StateServiceAbstraction,
organizationService: OrganizationServiceAbstraction,
providerService: ProviderServiceAbstraction
) =>
new SyncService(
apiService,
settingsService,
folderService,
cipherService,
cryptoService,
collectionService,
messagingService,
policyService,
sendService,
logService,
keyConnectorService,
stateService,
organizationService,
providerService,
async (expired: boolean) => messagingService.send("logout", { expired: expired })
),
deps: [
ApiServiceAbstraction,
SettingsServiceAbstraction,
FolderServiceAbstraction,
CipherServiceAbstraction,
CryptoServiceAbstraction,
CollectionServiceAbstraction,
MessagingServiceAbstraction,
PolicyServiceAbstraction,
SendServiceAbstraction,
LogService,
KeyConnectorServiceAbstraction,
StateServiceAbstraction,
OrganizationServiceAbstraction,
ProviderServiceAbstraction,
],
},
{ provide: BroadcasterServiceAbstraction, useClass: BroadcasterService },
{
provide: SettingsServiceAbstraction,
useClass: SettingsService,
deps: [StateServiceAbstraction],
},
{
provide: VaultTimeoutServiceAbstraction,
useFactory: (
cipherService: CipherServiceAbstraction,
folderService: FolderServiceAbstraction,
collectionService: CollectionServiceAbstraction,
cryptoService: CryptoServiceAbstraction,
platformUtilsService: PlatformUtilsServiceAbstraction,
messagingService: MessagingServiceAbstraction,
searchService: SearchServiceAbstraction,
tokenService: TokenServiceAbstraction,
policyService: PolicyServiceAbstraction,
keyConnectorService: KeyConnectorServiceAbstraction,
stateService: StateServiceAbstraction
) =>
new VaultTimeoutService(
cipherService,
folderService,
collectionService,
cryptoService,
platformUtilsService,
messagingService,
searchService,
tokenService,
policyService,
keyConnectorService,
stateService,
null,
async () => messagingService.send("logout", { expired: false })
),
deps: [
CipherServiceAbstraction,
FolderServiceAbstraction,
CollectionServiceAbstraction,
CryptoServiceAbstraction,
PlatformUtilsServiceAbstraction,
MessagingServiceAbstraction,
SearchServiceAbstraction,
TokenServiceAbstraction,
PolicyServiceAbstraction,
KeyConnectorServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: StateServiceAbstraction,
useClass: StateService,
deps: [
StorageServiceAbstraction,
"SECURE_STORAGE",
LogService,
StateMigrationServiceAbstraction,
],
},
{
provide: StateMigrationServiceAbstraction,
useClass: StateMigrationService,
deps: [StorageServiceAbstraction, "SECURE_STORAGE"],
},
{
provide: ExportServiceAbstraction,
useClass: ExportService,
deps: [
FolderServiceAbstraction,
CipherServiceAbstraction,
ApiServiceAbstraction,
CryptoServiceAbstraction,
],
},
{
provide: SearchServiceAbstraction,
useClass: SearchService,
deps: [CipherServiceAbstraction, LogService, I18nServiceAbstraction],
},
{
provide: NotificationsServiceAbstraction,
useFactory: (
syncService: SyncServiceAbstraction,
appIdService: AppIdServiceAbstraction,
apiService: ApiServiceAbstraction,
vaultTimeoutService: VaultTimeoutServiceAbstraction,
environmentService: EnvironmentServiceAbstraction,
messagingService: MessagingServiceAbstraction,
logService: LogService,
stateService: StateServiceAbstraction
) =>
new NotificationsService(
syncService,
appIdService,
apiService,
vaultTimeoutService,
environmentService,
async () => messagingService.send("logout", { expired: true }),
logService,
stateService
),
deps: [
SyncServiceAbstraction,
AppIdServiceAbstraction,
ApiServiceAbstraction,
VaultTimeoutServiceAbstraction,
EnvironmentServiceAbstraction,
MessagingServiceAbstraction,
LogService,
StateServiceAbstraction,
],
},
{
provide: CryptoFunctionServiceAbstraction,
useClass: WebCryptoFunctionService,
deps: ["WINDOW", PlatformUtilsServiceAbstraction],
},
{
provide: EventServiceAbstraction,
useClass: EventService,
deps: [
ApiServiceAbstraction,
CipherServiceAbstraction,
StateServiceAbstraction,
LogService,
OrganizationServiceAbstraction,
],
},
{
provide: PolicyServiceAbstraction,
useClass: PolicyService,
deps: [StateServiceAbstraction, OrganizationServiceAbstraction, ApiServiceAbstraction],
},
{
provide: SendServiceAbstraction,
useClass: SendService,
deps: [
CryptoServiceAbstraction,
ApiServiceAbstraction,
FileUploadServiceAbstraction,
I18nServiceAbstraction,
CryptoFunctionServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: KeyConnectorServiceAbstraction,
useClass: KeyConnectorService,
deps: [
StateServiceAbstraction,
CryptoServiceAbstraction,
ApiServiceAbstraction,
TokenServiceAbstraction,
LogService,
OrganizationServiceAbstraction,
],
},
{
provide: UserVerificationServiceAbstraction,
useClass: UserVerificationService,
deps: [CryptoServiceAbstraction, I18nServiceAbstraction, ApiServiceAbstraction],
},
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
{
provide: OrganizationServiceAbstraction,
useClass: OrganizationService,
deps: [StateServiceAbstraction],
},
{
provide: ProviderServiceAbstraction,
useClass: ProviderService,
deps: [StateServiceAbstraction],
},
],
})
export class JslibServicesModule {
}
export class JslibServicesModule {}

View File

@@ -1,29 +1,29 @@
import { Injectable } from '@angular/core';
import {
CanActivate,
Router,
} from '@angular/router';
import { Injectable } from "@angular/core";
import { CanActivate, Router } from "@angular/router";
import { StateService } from 'jslib-common/abstractions/state.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
@Injectable()
export class LockGuardService implements CanActivate {
protected homepage = 'vault';
constructor(private vaultTimeoutService: VaultTimeoutService, private router: Router,
private stateService: StateService) { }
protected homepage = "vault";
constructor(
private vaultTimeoutService: VaultTimeoutService,
private router: Router,
private stateService: StateService
) {}
async canActivate() {
if (!await this.stateService.getIsAuthenticated()) {
this.router.navigate(['login']);
return false;
}
if (!await this.vaultTimeoutService.isLocked()) {
this.router.navigate([this.homepage]);
return false;
}
return true;
async canActivate() {
if (!(await this.stateService.getIsAuthenticated())) {
this.router.navigate(["login"]);
return false;
}
if (!(await this.vaultTimeoutService.isLocked())) {
this.router.navigate([this.homepage]);
return false;
}
return true;
}
}

View File

@@ -1,164 +1,179 @@
import {
ApplicationRef,
ComponentFactory,
ComponentFactoryResolver,
ComponentRef,
EmbeddedViewRef,
Injectable,
Injector,
Type,
ViewContainerRef
} from '@angular/core';
import { first } from 'rxjs/operators';
ApplicationRef,
ComponentFactory,
ComponentFactoryResolver,
ComponentRef,
EmbeddedViewRef,
Injectable,
Injector,
Type,
ViewContainerRef,
} from "@angular/core";
import { first } from "rxjs/operators";
import { DynamicModalComponent } from '../components/modal/dynamic-modal.component';
import { ModalInjector } from '../components/modal/modal-injector';
import { ModalRef } from '../components/modal/modal.ref';
import { DynamicModalComponent } from "../components/modal/dynamic-modal.component";
import { ModalInjector } from "../components/modal/modal-injector";
import { ModalRef } from "../components/modal/modal.ref";
export class ModalConfig<D = any> {
data?: D;
allowMultipleModals: boolean = false;
data?: D;
allowMultipleModals: boolean = false;
}
@Injectable()
export class ModalService {
protected modalList: ComponentRef<DynamicModalComponent>[] = [];
protected modalList: ComponentRef<DynamicModalComponent>[] = [];
// Lazy loaded modules are not available in componentFactoryResolver,
// therefore modules needs to manually initialize their resolvers.
private factoryResolvers: Map<Type<any>, ComponentFactoryResolver> = new Map();
// Lazy loaded modules are not available in componentFactoryResolver,
// therefore modules needs to manually initialize their resolvers.
private factoryResolvers: Map<Type<any>, ComponentFactoryResolver> = new Map();
constructor(private componentFactoryResolver: ComponentFactoryResolver, private applicationRef: ApplicationRef,
private injector: Injector) {
document.addEventListener('keyup', event => {
if (event.key === 'Escape' && this.modalCount > 0) {
this.topModal.instance.close();
}
constructor(
private componentFactoryResolver: ComponentFactoryResolver,
private applicationRef: ApplicationRef,
private injector: Injector
) {
document.addEventListener("keyup", (event) => {
if (event.key === "Escape" && this.modalCount > 0) {
this.topModal.instance.close();
}
});
}
get modalCount() {
return this.modalList.length;
}
private get topModal() {
return this.modalList[this.modalCount - 1];
}
async openViewRef<T>(
componentType: Type<T>,
viewContainerRef: ViewContainerRef,
setComponentParameters: (component: T) => void = null
): Promise<[ModalRef, T]> {
const [modalRef, modalComponentRef] = this.openInternal(componentType, null, false);
modalComponentRef.instance.setComponentParameters = setComponentParameters;
viewContainerRef.insert(modalComponentRef.hostView);
await modalRef.onCreated.pipe(first()).toPromise();
return [modalRef, modalComponentRef.instance.componentRef.instance];
}
open(componentType: Type<any>, config?: ModalConfig) {
if (!(config?.allowMultipleModals ?? false) && this.modalCount > 0) {
return;
}
const [modalRef, _] = this.openInternal(componentType, config, true);
return modalRef;
}
registerComponentFactoryResolver<T>(
componentType: Type<T>,
componentFactoryResolver: ComponentFactoryResolver
): void {
this.factoryResolvers.set(componentType, componentFactoryResolver);
}
resolveComponentFactory<T>(componentType: Type<T>): ComponentFactory<T> {
if (this.factoryResolvers.has(componentType)) {
return this.factoryResolvers.get(componentType).resolveComponentFactory(componentType);
}
return this.componentFactoryResolver.resolveComponentFactory(componentType);
}
protected openInternal(
componentType: Type<any>,
config?: ModalConfig,
attachToDom?: boolean
): [ModalRef, ComponentRef<DynamicModalComponent>] {
const [modalRef, componentRef] = this.createModalComponent(config);
componentRef.instance.childComponentType = componentType;
if (attachToDom) {
this.applicationRef.attachView(componentRef.hostView);
const domElem = (componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0] as HTMLElement;
document.body.appendChild(domElem);
}
modalRef.onClosed.pipe(first()).subscribe(() => {
if (attachToDom) {
this.applicationRef.detachView(componentRef.hostView);
}
componentRef.destroy();
this.modalList.pop();
if (this.modalCount > 0) {
this.topModal.instance.getFocus();
}
});
this.setupHandlers(modalRef);
this.modalList.push(componentRef);
return [modalRef, componentRef];
}
protected setupHandlers(modalRef: ModalRef) {
let backdrop: HTMLElement = null;
// Add backdrop, setup [data-dismiss] handler.
modalRef.onCreated.pipe(first()).subscribe((el) => {
document.body.classList.add("modal-open");
const modalEl: HTMLElement = el.querySelector(".modal");
const dialogEl = modalEl.querySelector(".modal-dialog") as HTMLElement;
backdrop = document.createElement("div");
backdrop.className = "modal-backdrop fade";
backdrop.style.zIndex = `${this.modalCount}040`;
modalEl.prepend(backdrop);
dialogEl.addEventListener("click", (e: Event) => {
e.stopPropagation();
});
dialogEl.style.zIndex = `${this.modalCount}050`;
const modals = Array.from(
el.querySelectorAll('.modal-backdrop, .modal *[data-dismiss="modal"]')
);
for (const closeElement of modals) {
closeElement.addEventListener("click", (event) => {
modalRef.close();
});
}
}
});
get modalCount() {
return this.modalList.length;
}
// onClose is used in Web to hook into bootstrap. On other projects we pipe it directly to closed.
modalRef.onClose.pipe(first()).subscribe(() => {
modalRef.closed();
private get topModal() {
return this.modalList[this.modalCount - 1];
}
if (this.modalCount === 0) {
document.body.classList.remove("modal-open");
}
});
}
async openViewRef<T>(componentType: Type<T>, viewContainerRef: ViewContainerRef,
setComponentParameters: (component: T) => void = null): Promise<[ModalRef, T]> {
protected createModalComponent(
config: ModalConfig
): [ModalRef, ComponentRef<DynamicModalComponent>] {
const modalRef = new ModalRef();
const [modalRef, modalComponentRef] = this.openInternal(componentType, null, false);
modalComponentRef.instance.setComponentParameters = setComponentParameters;
const map = new WeakMap();
map.set(ModalConfig, config);
map.set(ModalRef, modalRef);
viewContainerRef.insert(modalComponentRef.hostView);
const componentFactory =
this.componentFactoryResolver.resolveComponentFactory(DynamicModalComponent);
const componentRef = componentFactory.create(new ModalInjector(this.injector, map));
await modalRef.onCreated.pipe(first()).toPromise();
return [modalRef, modalComponentRef.instance.componentRef.instance];
}
open(componentType: Type<any>, config?: ModalConfig) {
if (!(config?.allowMultipleModals ?? false) && this.modalCount > 0) {
return;
}
const [modalRef, _] = this.openInternal(componentType, config, true);
return modalRef;
}
registerComponentFactoryResolver<T>(componentType: Type<T>, componentFactoryResolver: ComponentFactoryResolver): void {
this.factoryResolvers.set(componentType, componentFactoryResolver);
}
resolveComponentFactory<T>(componentType: Type<T>): ComponentFactory<T> {
if (this.factoryResolvers.has(componentType)) {
return this.factoryResolvers.get(componentType).resolveComponentFactory(componentType);
}
return this.componentFactoryResolver.resolveComponentFactory(componentType);
}
protected openInternal(componentType: Type<any>, config?: ModalConfig, attachToDom?: boolean):
[ModalRef, ComponentRef<DynamicModalComponent>] {
const [modalRef, componentRef] = this.createModalComponent(config);
componentRef.instance.childComponentType = componentType;
if (attachToDom) {
this.applicationRef.attachView(componentRef.hostView);
const domElem = (componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0] as HTMLElement;
document.body.appendChild(domElem);
}
modalRef.onClosed.pipe(first()).subscribe(() => {
if (attachToDom) {
this.applicationRef.detachView(componentRef.hostView);
}
componentRef.destroy();
this.modalList.pop();
if (this.modalCount > 0) {
this.topModal.instance.getFocus();
}
});
this.setupHandlers(modalRef);
this.modalList.push(componentRef);
return [modalRef, componentRef];
}
protected setupHandlers(modalRef: ModalRef) {
let backdrop: HTMLElement = null;
// Add backdrop, setup [data-dismiss] handler.
modalRef.onCreated.pipe(first()).subscribe(el => {
document.body.classList.add('modal-open');
const modalEl: HTMLElement = el.querySelector('.modal');
const dialogEl = modalEl.querySelector('.modal-dialog') as HTMLElement;
backdrop = document.createElement('div');
backdrop.className = 'modal-backdrop fade';
backdrop.style.zIndex = `${this.modalCount}040`;
modalEl.prepend(backdrop);
dialogEl.addEventListener('click', (e: Event) => {
e.stopPropagation();
});
dialogEl.style.zIndex = `${this.modalCount}050`;
const modals = Array.from(el.querySelectorAll('.modal-backdrop, .modal *[data-dismiss="modal"]'));
for (const closeElement of modals) {
closeElement.addEventListener('click', event => {
modalRef.close();
});
}
});
// onClose is used in Web to hook into bootstrap. On other projects we pipe it directly to closed.
modalRef.onClose.pipe(first()).subscribe(() => {
modalRef.closed();
if (this.modalCount === 0) {
document.body.classList.remove('modal-open');
}
});
}
protected createModalComponent(config: ModalConfig): [ModalRef, ComponentRef<DynamicModalComponent>] {
const modalRef = new ModalRef();
const map = new WeakMap();
map.set(ModalConfig, config);
map.set(ModalRef, modalRef);
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(DynamicModalComponent);
const componentRef = componentFactory.create(new ModalInjector(this.injector, map));
return [modalRef, componentRef];
}
return [modalRef, componentRef];
}
}

View File

@@ -1,37 +1,40 @@
import { Injectable } from '@angular/core';
import { Injectable } from "@angular/core";
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from 'jslib-common/abstractions/passwordReprompt.service';
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "jslib-common/abstractions/passwordReprompt.service";
import { PasswordRepromptComponent } from '../components/password-reprompt.component';
import { ModalService } from './modal.service';
import { PasswordRepromptComponent } from "../components/password-reprompt.component";
import { ModalService } from "./modal.service";
@Injectable()
export class PasswordRepromptService implements PasswordRepromptServiceAbstraction {
protected component = PasswordRepromptComponent;
protected component = PasswordRepromptComponent;
constructor(private modalService: ModalService, private keyConnectorService: KeyConnectorService) { }
constructor(
private modalService: ModalService,
private keyConnectorService: KeyConnectorService
) {}
protectedFields() {
return ['TOTP', 'Password', 'H_Field', 'Card Number', 'Security Code'];
protectedFields() {
return ["TOTP", "Password", "H_Field", "Card Number", "Security Code"];
}
async showPasswordPrompt() {
if (!(await this.enabled())) {
return true;
}
async showPasswordPrompt() {
if (!await this.enabled()) {
return true;
}
const ref = this.modalService.open(this.component, { allowMultipleModals: true });
const ref = this.modalService.open(this.component, {allowMultipleModals: true});
if (ref == null) {
return false;
}
const result = await ref.onClosedPromise();
return result === true;
if (ref == null) {
return false;
}
async enabled() {
return !await this.keyConnectorService.getUsesKeyConnector();
}
const result = await ref.onClosedPromise();
return result === true;
}
async enabled() {
return !(await this.keyConnectorService.getUsesKeyConnector());
}
}

View File

@@ -1,30 +1,29 @@
import { Injectable } from '@angular/core';
import {
CanActivate,
Router,
} from '@angular/router';
import { Injectable } from "@angular/core";
import { CanActivate, Router } from "@angular/router";
import { StateService } from 'jslib-common/abstractions/state.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
@Injectable()
export class UnauthGuardService implements CanActivate {
protected homepage = "vault";
constructor(
private vaultTimeoutService: VaultTimeoutService,
private router: Router,
private stateService: StateService
) {}
protected homepage = 'vault';
constructor(private vaultTimeoutService: VaultTimeoutService, private router: Router,
private stateService: StateService) { }
async canActivate() {
const isAuthed = await this.stateService.getIsAuthenticated();
if (isAuthed) {
const locked = await this.vaultTimeoutService.isLocked();
if (locked) {
this.router.navigate(['lock']);
} else {
this.router.navigate([this.homepage]);
}
return false;
}
return true;
async canActivate() {
const isAuthed = await this.stateService.getIsAuthenticated();
if (isAuthed) {
const locked = await this.vaultTimeoutService.isLocked();
if (locked) {
this.router.navigate(["lock"]);
} else {
this.router.navigate([this.homepage]);
}
return false;
}
return true;
}
}

View File

@@ -1,36 +1,39 @@
import { Injectable } from '@angular/core';
import { Injectable } from "@angular/core";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ErrorResponse } from 'jslib-common/models/response/errorResponse';
import { ErrorResponse } from "jslib-common/models/response/errorResponse";
@Injectable()
export class ValidationService {
constructor(private i18nService: I18nService, private platformUtilsService: PlatformUtilsService) { }
constructor(
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService
) {}
showError(data: any): string[] {
const defaultErrorMessage = this.i18nService.t('unexpectedError');
let errors: string[] = [];
showError(data: any): string[] {
const defaultErrorMessage = this.i18nService.t("unexpectedError");
let errors: string[] = [];
if (data != null && typeof data === 'string') {
errors.push(data);
} else if (data == null || typeof data !== 'object') {
errors.push(defaultErrorMessage);
} else if (data.validationErrors != null) {
errors = errors.concat((data as ErrorResponse).getAllMessages());
} else {
errors.push(data.message ? data.message : defaultErrorMessage);
}
if (errors.length === 1) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), errors[0]);
} else if (errors.length > 1) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), errors, {
timeout: 5000 * errors.length,
});
}
return errors;
if (data != null && typeof data === "string") {
errors.push(data);
} else if (data == null || typeof data !== "object") {
errors.push(defaultErrorMessage);
} else if (data.validationErrors != null) {
errors = errors.concat((data as ErrorResponse).getAllMessages());
} else {
errors.push(data.message ? data.message : defaultErrorMessage);
}
if (errors.length === 1) {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), errors[0]);
} else if (errors.length > 1) {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), errors, {
timeout: 5000 * errors.length,
});
}
return errors;
}
}

View File

@@ -14,17 +14,9 @@
"declarationDir": "dist/types",
"outDir": "dist",
"paths": {
"jslib-common/*": [
"../common/src/*"
]
"jslib-common/*": ["../common/src/*"]
}
},
"include": [
"src",
"spec"
],
"exclude": [
"node_modules",
"dist"
]
"include": ["src", "spec"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -1,106 +1,105 @@
const path = require('path');
const webpack = require('webpack');
const path = require("path");
const webpack = require("webpack");
module.exports = (config) => {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: "",
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine', 'webpack', 'detectBrowsers'],
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ["jasmine", "webpack", "detectBrowsers"],
// list of files / patterns to load in the browser
files: [
{ pattern: 'spec/**/*.spec.ts', watched: false },
// list of files / patterns to load in the browser
files: [{ pattern: "spec/**/*.spec.ts", watched: false }],
// list of files to exclude
exclude: [],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
"spec/**/*.ts": ["webpack", "sourcemap"],
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ["progress", "kjhtml"],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity,
client: {
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
webpack: {
mode: "development",
resolve: {
extensions: [".js", ".ts", ".tsx"],
fallback: {
util: require.resolve("util/"),
url: require.resolve("url/"),
},
alias: {
src: path.resolve(__dirname, "src/"),
},
},
plugins: [
new webpack.ProvidePlugin({
process: "process/browser",
}),
],
module: {
rules: [
{
test: /\.tsx?$/,
loader: "ts-loader",
},
],
// list of files to exclude
exclude: [],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
'spec/**/*.ts': ['webpack', 'sourcemap'],
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress', 'kjhtml'],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
},
stats: {
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity,
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
webpack: {
mode: 'development',
resolve: {
extensions: ['.js', '.ts', '.tsx'],
fallback: {
"util": require.resolve("util/"),
"url": require.resolve("url/"),
},
alias: {
src: path.resolve(__dirname, 'src/'),
},
},
plugins: [
new webpack.ProvidePlugin({
process: 'process/browser',
}),
],
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'ts-loader',
},
],
},
stats: {
colors: true,
modules: true,
reasons: true,
errorDetails: true,
},
},
detectBrowsers: {
usePhantomJS: false,
postDetection: (availableBrowsers) => {
const result = availableBrowsers;
function removeBrowser(browser) {
if (availableBrowsers.length > 1 && availableBrowsers.indexOf(browser) > -1) {
result.splice(result.indexOf(browser), 1);
}
}
removeBrowser('IE');
removeBrowser('Opera');
removeBrowser('SafariTechPreview');
var githubAction = process.env.GITHUB_WORKFLOW != null && process.env.GITHUB_WORKFLOW !== '';
if (githubAction) {
removeBrowser('Firefox');
removeBrowser('Safari');
}
return result;
}
modules: true,
reasons: true,
errorDetails: true,
},
},
detectBrowsers: {
usePhantomJS: false,
postDetection: (availableBrowsers) => {
const result = availableBrowsers;
function removeBrowser(browser) {
if (availableBrowsers.length > 1 && availableBrowsers.indexOf(browser) > -1) {
result.splice(result.indexOf(browser), 1);
}
}
})
}
removeBrowser("IE");
removeBrowser("Opera");
removeBrowser("SafariTechPreview");
var githubAction =
process.env.GITHUB_WORKFLOW != null && process.env.GITHUB_WORKFLOW !== "";
if (githubAction) {
removeBrowser("Firefox");
removeBrowser("Safari");
}
return result;
},
},
});
};

View File

@@ -1,9 +1,9 @@
// tslint:disable-next-line
const TSConsoleReporter = require('jasmine-ts-console-reporter');
const TSConsoleReporter = require("jasmine-ts-console-reporter");
jasmine.getEnv().clearReporters(); // Clear default console reporter
jasmine.getEnv().addReporter(new TSConsoleReporter());
// Polyfills
// tslint:disable-next-line
const jsdom: any = require('jsdom');
const jsdom: any = require("jsdom");
(global as any).DOMParser = new jsdom.JSDOM().window.DOMParser;

View File

@@ -1,73 +1,73 @@
import { FirefoxCsvImporter as Importer } from 'src/importers/firefoxCsvImporter';
import { FirefoxCsvImporter as Importer } from "src/importers/firefoxCsvImporter";
import { CipherView } from 'src/models/view/cipherView';
import { LoginUriView } from 'src/models/view/loginUriView';
import { LoginView } from 'src/models/view/loginView';
import { CipherView } from "src/models/view/cipherView";
import { LoginUriView } from "src/models/view/loginUriView";
import { LoginView } from "src/models/view/loginView";
import { data as firefoxAccountsData } from './testData/firefoxCsv/firefoxAccountsData.csv';
import { data as simplePasswordData } from './testData/firefoxCsv/simplePasswordData.csv';
import { data as firefoxAccountsData } from "./testData/firefoxCsv/firefoxAccountsData.csv";
import { data as simplePasswordData } from "./testData/firefoxCsv/simplePasswordData.csv";
const CipherData = [
{
title: 'should parse password',
csv: simplePasswordData,
expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: 'example.com',
login: Object.assign(new LoginView(), {
username: 'foo',
password: 'bar',
uris: [
Object.assign(new LoginUriView(), {
uri: 'https://example.com',
}),
],
}),
notes: null,
type: 1,
}),
},
{
title: 'should skip "chrome://FirefoxAccounts"',
csv: firefoxAccountsData,
expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: 'example.com',
login: Object.assign(new LoginView(), {
username: 'foo',
password: 'bar',
uris: [
Object.assign(new LoginUriView(), {
uri: 'https://example.com',
}),
],
}),
notes: null,
type: 1,
}),
},
{
title: "should parse password",
csv: simplePasswordData,
expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "example.com",
login: Object.assign(new LoginView(), {
username: "foo",
password: "bar",
uris: [
Object.assign(new LoginUriView(), {
uri: "https://example.com",
}),
],
}),
notes: null,
type: 1,
}),
},
{
title: 'should skip "chrome://FirefoxAccounts"',
csv: firefoxAccountsData,
expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "example.com",
login: Object.assign(new LoginView(), {
username: "foo",
password: "bar",
uris: [
Object.assign(new LoginUriView(), {
uri: "https://example.com",
}),
],
}),
notes: null,
type: 1,
}),
},
];
describe('Firefox CSV Importer', () => {
CipherData.forEach(data => {
it(data.title, async () => {
const importer = new Importer();
const result = await importer.parse(data.csv);
expect(result != null).toBe(true);
expect(result.ciphers.length).toBeGreaterThan(0);
describe("Firefox CSV Importer", () => {
CipherData.forEach((data) => {
it(data.title, async () => {
const importer = new Importer();
const result = await importer.parse(data.csv);
expect(result != null).toBe(true);
expect(result.ciphers.length).toBeGreaterThan(0);
const cipher = result.ciphers.shift();
let property: keyof typeof data.expected;
for (property in data.expected) {
if (data.expected.hasOwnProperty(property)) {
expect(cipher.hasOwnProperty(property)).toBe(true);
expect(cipher[property]).toEqual(data.expected[property]);
}
}
});
const cipher = result.ciphers.shift();
let property: keyof typeof data.expected;
for (property in data.expected) {
if (data.expected.hasOwnProperty(property)) {
expect(cipher.hasOwnProperty(property)).toBe(true);
expect(cipher[property]).toEqual(data.expected[property]);
}
}
});
});
});

View File

@@ -1,80 +1,77 @@
import { FSecureFskImporter as Importer } from 'src/importers/fsecureFskImporter';
import { FSecureFskImporter as Importer } from "src/importers/fsecureFskImporter";
const TestDataWithStyleSetToWebsite: string =
JSON.stringify({
data: {
'8d58b5cf252dd06fbd98f5289e918ab1': {
color: '#00baff',
reatedDate: 1609302913,
creditCvv: '',
creditExpiry: '',
creditNumber: '',
favorite: 0,
modifiedDate: 1609302913,
notes: 'note',
password: 'word',
passwordList: [],
passwordModifiedDate: 1609302913,
rev: 1,
service: 'My first pass',
style: 'website',
type: 1,
url: 'https://bitwarden.com',
username: 'pass',
},
},
});
const TestDataWithStyleSetToGlobe: string =
JSON.stringify({
data: {
'8d58b5cf252dd06fbd98f5289e918ab1': {
color: '#00baff',
reatedDate: 1609302913,
creditCvv: '',
creditExpiry: '',
creditNumber: '',
favorite: 0,
modifiedDate: 1609302913,
notes: 'note',
password: 'word',
passwordList: [],
passwordModifiedDate: 1609302913,
rev: 1,
service: 'My first pass',
style: 'globe',
type: 1,
url: 'https://bitwarden.com',
username: 'pass',
},
},
});
describe('FSecure FSK Importer', () => {
it('should parse data with style set to website', async () => {
const importer = new Importer();
const result = await importer.parse(TestDataWithStyleSetToWebsite);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.login.username).toEqual('pass');
expect(cipher.login.password).toEqual('word');
expect(cipher.login.uris.length).toEqual(1);
const uriView = cipher.login.uris.shift();
expect(uriView.uri).toEqual('https://bitwarden.com');
});
it('should parse data with style set to globe', async () => {
const importer = new Importer();
const result = await importer.parse(TestDataWithStyleSetToGlobe);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.login.username).toEqual('pass');
expect(cipher.login.password).toEqual('word');
expect(cipher.login.uris.length).toEqual(1);
const uriView = cipher.login.uris.shift();
expect(uriView.uri).toEqual('https://bitwarden.com');
});
const TestDataWithStyleSetToWebsite: string = JSON.stringify({
data: {
"8d58b5cf252dd06fbd98f5289e918ab1": {
color: "#00baff",
reatedDate: 1609302913,
creditCvv: "",
creditExpiry: "",
creditNumber: "",
favorite: 0,
modifiedDate: 1609302913,
notes: "note",
password: "word",
passwordList: [],
passwordModifiedDate: 1609302913,
rev: 1,
service: "My first pass",
style: "website",
type: 1,
url: "https://bitwarden.com",
username: "pass",
},
},
});
const TestDataWithStyleSetToGlobe: string = JSON.stringify({
data: {
"8d58b5cf252dd06fbd98f5289e918ab1": {
color: "#00baff",
reatedDate: 1609302913,
creditCvv: "",
creditExpiry: "",
creditNumber: "",
favorite: 0,
modifiedDate: 1609302913,
notes: "note",
password: "word",
passwordList: [],
passwordModifiedDate: 1609302913,
rev: 1,
service: "My first pass",
style: "globe",
type: 1,
url: "https://bitwarden.com",
username: "pass",
},
},
});
describe("FSecure FSK Importer", () => {
it("should parse data with style set to website", async () => {
const importer = new Importer();
const result = await importer.parse(TestDataWithStyleSetToWebsite);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.login.username).toEqual("pass");
expect(cipher.login.password).toEqual("word");
expect(cipher.login.uris.length).toEqual(1);
const uriView = cipher.login.uris.shift();
expect(uriView.uri).toEqual("https://bitwarden.com");
});
it("should parse data with style set to globe", async () => {
const importer = new Importer();
const result = await importer.parse(TestDataWithStyleSetToGlobe);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.login.username).toEqual("pass");
expect(cipher.login.password).toEqual("word");
expect(cipher.login.uris.length).toEqual(1);
const uriView = cipher.login.uris.shift();
expect(uriView.uri).toEqual("https://bitwarden.com");
});
});

View File

@@ -1,4 +1,4 @@
import { KeePass2XmlImporter as Importer } from 'src/importers/keepass2XmlImporter';
import { KeePass2XmlImporter as Importer } from "src/importers/keepass2XmlImporter";
const TestData: string = `<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<KeePassFile>
@@ -180,10 +180,10 @@ line2</Value>
</Root>
</KeePassFile>`;
describe('KeePass2 Xml Importer', () => {
it('should parse XML data', async () => {
const importer = new Importer();
const result = await importer.parse(TestData);
expect(result != null).toBe(true);
});
describe("KeePass2 Xml Importer", () => {
it("should parse XML data", async () => {
const importer = new Importer();
const result = await importer.parse(TestData);
expect(result != null).toBe(true);
});
});

View File

@@ -1,33 +1,33 @@
import { LastPassCsvImporter as Importer } from 'src/importers/lastpassCsvImporter';
import { LastPassCsvImporter as Importer } from "src/importers/lastpassCsvImporter";
import { ImportResult } from 'src/models/domain/importResult';
import { CipherView } from 'src/models/view/cipherView';
import { FieldView } from 'src/models/view/fieldView';
import { ImportResult } from "src/models/domain/importResult";
import { CipherView } from "src/models/view/cipherView";
import { FieldView } from "src/models/view/fieldView";
import { CipherType } from 'src/enums/cipherType';
import { FieldType } from 'src/enums/fieldType';
import { CipherType } from "src/enums/cipherType";
import { FieldType } from "src/enums/fieldType";
function baseExcept(result: ImportResult) {
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
}
function expectLogin(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Login);
expect(cipher.type).toBe(CipherType.Login);
expect(cipher.name).toBe('example.com');
expect(cipher.notes).toBe('super secure notes');
expect(cipher.login.uri).toBe('http://example.com');
expect(cipher.login.username).toBe('someUser');
expect(cipher.login.password).toBe('myPassword');
expect(cipher.login.totp).toBe('Y64VEVMBTSXCYIWRSHRNDZW62MPGVU2G');
expect(cipher.name).toBe("example.com");
expect(cipher.notes).toBe("super secure notes");
expect(cipher.login.uri).toBe("http://example.com");
expect(cipher.login.username).toBe("someUser");
expect(cipher.login.password).toBe("myPassword");
expect(cipher.login.totp).toBe("Y64VEVMBTSXCYIWRSHRNDZW62MPGVU2G");
}
const CipherData = [
{
title: 'should parse expiration date',
csv: `url,username,password,extra,name,grouping,fav
{
title: "should parse expiration date",
csv: `url,username,password,extra,name,grouping,fav
http://sn,,,"NoteType:Credit Card
Name on Card:John Doe
Type:
@@ -37,32 +37,32 @@ Start Date:October,2017
Expiration Date:June,2020
Notes:some text
",Credit-card,,0`,
expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: 'Credit-card',
notes: 'some text\n',
type: 3,
card: {
cardholderName: 'John Doe',
number: '1234567812345678',
code: '123',
expYear: '2020',
expMonth: '6',
},
fields: [
Object.assign(new FieldView(), {
name: 'Start Date',
value: 'October,2017',
type: FieldType.Text,
}),
],
expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "Credit-card",
notes: "some text\n",
type: 3,
card: {
cardholderName: "John Doe",
number: "1234567812345678",
code: "123",
expYear: "2020",
expMonth: "6",
},
fields: [
Object.assign(new FieldView(), {
name: "Start Date",
value: "October,2017",
type: FieldType.Text,
}),
},
{
title: 'should parse blank card note',
csv: `url,username,password,extra,name,grouping,fav
],
}),
},
{
title: "should parse blank card note",
csv: `url,username,password,extra,name,grouping,fav
http://sn,,,"NoteType:Credit Card
Name on Card:
Type:
@@ -71,28 +71,28 @@ Security Code:
Start Date:,
Expiration Date:,
Notes:",empty,,0`,
expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: 'empty',
notes: null,
type: 3,
card: {
expMonth: undefined,
},
fields: [
Object.assign(new FieldView(), {
name: 'Start Date',
value: ',',
type: FieldType.Text,
}),
],
expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "empty",
notes: null,
type: 3,
card: {
expMonth: undefined,
},
fields: [
Object.assign(new FieldView(), {
name: "Start Date",
value: ",",
type: FieldType.Text,
}),
},
{
title: 'should parse card expiration date w/ no exp year',
csv: `url,username,password,extra,name,grouping,fav
],
}),
},
{
title: "should parse card expiration date w/ no exp year",
csv: `url,username,password,extra,name,grouping,fav
http://sn,,,"NoteType:Credit Card
Name on Card:John Doe
Type:Visa
@@ -101,36 +101,36 @@ Security Code:321
Start Date:,
Expiration Date:January,
Notes:",noyear,,0`,
expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: 'noyear',
notes: null,
type: 3,
card: {
cardholderName: 'John Doe',
number: '1234567887654321',
code: '321',
expMonth: '1',
},
fields: [
Object.assign(new FieldView(), {
name: 'Type',
value: 'Visa',
type: FieldType.Text,
}),
Object.assign(new FieldView(), {
name: 'Start Date',
value: ',',
type: FieldType.Text,
}),
],
expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "noyear",
notes: null,
type: 3,
card: {
cardholderName: "John Doe",
number: "1234567887654321",
code: "321",
expMonth: "1",
},
fields: [
Object.assign(new FieldView(), {
name: "Type",
value: "Visa",
type: FieldType.Text,
}),
},
{
title: 'should parse card expiration date w/ no month',
csv: `url,username,password,extra,name,grouping,fav
Object.assign(new FieldView(), {
name: "Start Date",
value: ",",
type: FieldType.Text,
}),
],
}),
},
{
title: "should parse card expiration date w/ no month",
csv: `url,username,password,extra,name,grouping,fav
http://sn,,,"NoteType:Credit Card
Name on Card:John Doe
Type:Mastercard
@@ -139,64 +139,64 @@ Security Code:987
Start Date:,
Expiration Date:,2020
Notes:",nomonth,,0`,
expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: 'nomonth',
notes: null,
type: 3,
card: {
cardholderName: 'John Doe',
number: '8765432112345678',
code: '987',
expYear: '2020',
expMonth: undefined,
},
fields: [
Object.assign(new FieldView(), {
name: 'Type',
value: 'Mastercard',
type: FieldType.Text,
}),
Object.assign(new FieldView(), {
name: 'Start Date',
value: ',',
type: FieldType.Text,
}),
],
expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "nomonth",
notes: null,
type: 3,
card: {
cardholderName: "John Doe",
number: "8765432112345678",
code: "987",
expYear: "2020",
expMonth: undefined,
},
fields: [
Object.assign(new FieldView(), {
name: "Type",
value: "Mastercard",
type: FieldType.Text,
}),
},
Object.assign(new FieldView(), {
name: "Start Date",
value: ",",
type: FieldType.Text,
}),
],
}),
},
];
describe('Lastpass CSV Importer', () => {
CipherData.forEach(data => {
it(data.title, async () => {
const importer = new Importer();
const result = await importer.parse(data.csv);
expect(result != null).toBe(true);
expect(result.ciphers.length).toBeGreaterThan(0);
describe("Lastpass CSV Importer", () => {
CipherData.forEach((data) => {
it(data.title, async () => {
const importer = new Importer();
const result = await importer.parse(data.csv);
expect(result != null).toBe(true);
expect(result.ciphers.length).toBeGreaterThan(0);
const cipher = result.ciphers.shift();
let property: keyof typeof data.expected;
for (property in data.expected) {
if (data.expected.hasOwnProperty(property)) {
expect(cipher.hasOwnProperty(property)).toBe(true);
expect(cipher[property]).toEqual(data.expected[property]);
}
}
});
const cipher = result.ciphers.shift();
let property: keyof typeof data.expected;
for (property in data.expected) {
if (data.expected.hasOwnProperty(property)) {
expect(cipher.hasOwnProperty(property)).toBe(true);
expect(cipher[property]).toEqual(data.expected[property]);
}
}
});
});
it('should parse login with totp', async () => {
const input = `url,username,password,totp,extra,name,grouping,fav
it("should parse login with totp", async () => {
const input = `url,username,password,totp,extra,name,grouping,fav
http://example.com,someUser,myPassword,Y64VEVMBTSXCYIWRSHRNDZW62MPGVU2G,super secure notes,example.com,,0`;
const importer = new Importer();
const result = await importer.parse(input);
baseExcept(result);
const importer = new Importer();
const result = await importer.parse(input);
baseExcept(result);
const cipher = result.ciphers[0];
expectLogin(cipher);
});
const cipher = result.ciphers[0];
expectLogin(cipher);
});
});

View File

@@ -1,181 +1,182 @@
import { NordPassCsvImporter as Importer } from 'src/importers/nordpassCsvImporter';
import { NordPassCsvImporter as Importer } from "src/importers/nordpassCsvImporter";
import { CipherType } from 'src/enums/cipherType';
import { SecureNoteType } from 'src/enums/secureNoteType';
import { CipherView } from 'src/models/view/cipherView';
import { IdentityView } from 'src/models/view/identityView';
import { CipherType } from "src/enums/cipherType";
import { SecureNoteType } from "src/enums/secureNoteType";
import { CipherView } from "src/models/view/cipherView";
import { IdentityView } from "src/models/view/identityView";
import { data as creditCardData } from './testData/nordpassCsv/nordpass.card.csv';
import { data as identityData } from './testData/nordpassCsv/nordpass.identity.csv';
import { data as loginData } from './testData/nordpassCsv/nordpass.login.csv';
import { data as secureNoteData } from './testData/nordpassCsv/nordpass.secureNote.csv';
import { data as creditCardData } from "./testData/nordpassCsv/nordpass.card.csv";
import { data as identityData } from "./testData/nordpassCsv/nordpass.identity.csv";
import { data as loginData } from "./testData/nordpassCsv/nordpass.login.csv";
import { data as secureNoteData } from "./testData/nordpassCsv/nordpass.secureNote.csv";
const namesTestData = [
{
title: 'Given #fullName should set firstName',
fullName: 'MyFirstName',
expected: Object.assign(new IdentityView(), {
firstName: 'MyFirstName',
middleName: null,
lastName: null,
}),
},
{
title: 'Given #fullName should set first- and lastName',
fullName: 'MyFirstName MyLastName',
expected: Object.assign(new IdentityView(), {
firstName: 'MyFirstName',
middleName: null,
lastName: 'MyLastName',
}),
},
{
title: 'Given #fullName should set first-, middle and lastName',
fullName: 'MyFirstName MyMiddleName MyLastName',
expected: Object.assign(new IdentityView(), {
firstName: 'MyFirstName',
middleName: 'MyMiddleName',
lastName: 'MyLastName',
}),
},
{
title: 'Given #fullName should set first-, middle and lastName with Jr',
fullName: 'MyFirstName MyMiddleName MyLastName Jr',
expected: Object.assign(new IdentityView(), {
firstName: 'MyFirstName',
middleName: 'MyMiddleName',
lastName: 'MyLastName Jr',
}),
},
{
title: 'Given #fullName should set first-, middle and lastName with Jr and III',
fullName: 'MyFirstName MyMiddleName MyLastName Jr III',
expected: Object.assign(new IdentityView(), {
firstName: 'MyFirstName',
middleName: 'MyMiddleName',
lastName: 'MyLastName Jr III',
}),
},
{
title: "Given #fullName should set firstName",
fullName: "MyFirstName",
expected: Object.assign(new IdentityView(), {
firstName: "MyFirstName",
middleName: null,
lastName: null,
}),
},
{
title: "Given #fullName should set first- and lastName",
fullName: "MyFirstName MyLastName",
expected: Object.assign(new IdentityView(), {
firstName: "MyFirstName",
middleName: null,
lastName: "MyLastName",
}),
},
{
title: "Given #fullName should set first-, middle and lastName",
fullName: "MyFirstName MyMiddleName MyLastName",
expected: Object.assign(new IdentityView(), {
firstName: "MyFirstName",
middleName: "MyMiddleName",
lastName: "MyLastName",
}),
},
{
title: "Given #fullName should set first-, middle and lastName with Jr",
fullName: "MyFirstName MyMiddleName MyLastName Jr",
expected: Object.assign(new IdentityView(), {
firstName: "MyFirstName",
middleName: "MyMiddleName",
lastName: "MyLastName Jr",
}),
},
{
title: "Given #fullName should set first-, middle and lastName with Jr and III",
fullName: "MyFirstName MyMiddleName MyLastName Jr III",
expected: Object.assign(new IdentityView(), {
firstName: "MyFirstName",
middleName: "MyMiddleName",
lastName: "MyLastName Jr III",
}),
},
];
function expectLogin(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Login);
expect(cipher.type).toBe(CipherType.Login);
expect(cipher.name).toBe('SomeVaultItemName');
expect(cipher.notes).toBe('Some note for the VaultItem');
expect(cipher.login.uri).toBe('https://example.com');
expect(cipher.login.username).toBe('hello@bitwarden.com');
expect(cipher.login.password).toBe('someStrongPassword');
expect(cipher.name).toBe("SomeVaultItemName");
expect(cipher.notes).toBe("Some note for the VaultItem");
expect(cipher.login.uri).toBe("https://example.com");
expect(cipher.login.username).toBe("hello@bitwarden.com");
expect(cipher.login.password).toBe("someStrongPassword");
}
function expectCreditCard(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Card);
expect(cipher.type).toBe(CipherType.Card);
expect(cipher.name).toBe('SomeVisa');
expect(cipher.card.brand).toBe('Visa');
expect(cipher.card.cardholderName).toBe('SomeHolder');
expect(cipher.card.number).toBe('4024007103939509');
expect(cipher.card.code).toBe('123');
expect(cipher.card.expMonth).toBe('1');
expect(cipher.card.expYear).toBe('22');
expect(cipher.name).toBe("SomeVisa");
expect(cipher.card.brand).toBe("Visa");
expect(cipher.card.cardholderName).toBe("SomeHolder");
expect(cipher.card.number).toBe("4024007103939509");
expect(cipher.card.code).toBe("123");
expect(cipher.card.expMonth).toBe("1");
expect(cipher.card.expYear).toBe("22");
}
function expectIdentity(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.name).toBe('SomeTitle');
expect(cipher.identity.fullName).toBe('MyFirstName MyMiddleName MyLastName');
expect(cipher.identity.firstName).toBe('MyFirstName');
expect(cipher.identity.middleName).toBe('MyMiddleName');
expect(cipher.identity.lastName).toBe('MyLastName');
expect(cipher.identity.email).toBe('hello@bitwarden.com');
expect(cipher.identity.phone).toBe('123456789');
expect(cipher.name).toBe("SomeTitle");
expect(cipher.identity.fullName).toBe("MyFirstName MyMiddleName MyLastName");
expect(cipher.identity.firstName).toBe("MyFirstName");
expect(cipher.identity.middleName).toBe("MyMiddleName");
expect(cipher.identity.lastName).toBe("MyLastName");
expect(cipher.identity.email).toBe("hello@bitwarden.com");
expect(cipher.identity.phone).toBe("123456789");
expect(cipher.identity.address1).toBe('Test street 123');
expect(cipher.identity.address2).toBe('additional addressinfo');
expect(cipher.identity.postalCode).toBe('123456');
expect(cipher.identity.city).toBe('Cologne');
expect(cipher.identity.state).toBe('North-Rhine-Westphalia');
expect(cipher.identity.country).toBe('GERMANY');
expect(cipher.notes).toBe('SomeNoteToMyIdentity');
expect(cipher.identity.address1).toBe("Test street 123");
expect(cipher.identity.address2).toBe("additional addressinfo");
expect(cipher.identity.postalCode).toBe("123456");
expect(cipher.identity.city).toBe("Cologne");
expect(cipher.identity.state).toBe("North-Rhine-Westphalia");
expect(cipher.identity.country).toBe("GERMANY");
expect(cipher.notes).toBe("SomeNoteToMyIdentity");
}
function expectSecureNote(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.SecureNote);
expect(cipher.type).toBe(CipherType.SecureNote);
expect(cipher.name).toBe('MySuperSecureNoteTitle');
expect(cipher.secureNote.type).toBe(SecureNoteType.Generic);
expect(cipher.notes).toBe('MySuperSecureNote');
expect(cipher.name).toBe("MySuperSecureNoteTitle");
expect(cipher.secureNote.type).toBe(SecureNoteType.Generic);
expect(cipher.notes).toBe("MySuperSecureNote");
}
describe('NordPass CSV Importer', () => {
let importer: Importer;
beforeEach(() => {
importer = new Importer();
describe("NordPass CSV Importer", () => {
let importer: Importer;
beforeEach(() => {
importer = new Importer();
});
it("should parse login records", async () => {
const result = await importer.parse(loginData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expectLogin(cipher);
});
it("should parse credit card records", async () => {
const result = await importer.parse(creditCardData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expectCreditCard(cipher);
});
it("should parse identity records", async () => {
const result = await importer.parse(
identityData.replace("#fullName", "MyFirstName MyMiddleName MyLastName")
);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expectIdentity(cipher);
});
namesTestData.forEach((data) => {
it(data.title.replace("#fullName", data.fullName), async () => {
const result = await importer.parse(identityData.replace("#fullName", data.fullName));
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expect(cipher.identity.firstName).toBe(data.expected.firstName);
expect(cipher.identity.middleName).toBe(data.expected.middleName);
expect(cipher.identity.lastName).toBe(data.expected.lastName);
});
});
it('should parse login records', async () => {
const result = await importer.parse(loginData);
it("should parse secureNote records", async () => {
const result = await importer.parse(secureNoteData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expectLogin(cipher);
});
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expectSecureNote(cipher);
});
it('should parse credit card records', async () => {
const result = await importer.parse(creditCardData);
it("should parse an item and create a folder", async () => {
const result = await importer.parse(secureNoteData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expectCreditCard(cipher);
});
it('should parse identity records', async () => {
const result = await importer.parse(identityData.replace('#fullName', 'MyFirstName MyMiddleName MyLastName'));
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expectIdentity(cipher);
});
namesTestData.forEach(data => {
it(data.title.replace('#fullName', data.fullName), async () => {
const result = await importer.parse(identityData.replace('#fullName', data.fullName));
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expect(cipher.identity.firstName).toBe(data.expected.firstName);
expect(cipher.identity.middleName).toBe(data.expected.middleName);
expect(cipher.identity.lastName).toBe(data.expected.lastName);
});
});
it('should parse secureNote records', async () => {
const result = await importer.parse(secureNoteData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expectSecureNote(cipher);
});
it('should parse an item and create a folder', async () => {
const result = await importer.parse(secureNoteData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.folders.length).toBe(1);
const folder = result.folders[0];
expect(folder.name).toBe('notesFolder');
});
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.folders.length).toBe(1);
const folder = result.folders[0];
expect(folder.name).toBe("notesFolder");
});
});

View File

@@ -1,513 +1,514 @@
import { FieldType } from 'src/enums/fieldType';
import { OnePassword1PifImporter as Importer } from 'src/importers/onepasswordImporters/onepassword1PifImporter';
import { FieldType } from "src/enums/fieldType";
import { OnePassword1PifImporter as Importer } from "src/importers/onepasswordImporters/onepassword1PifImporter";
const TestData: string = '***aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee***\n' +
JSON.stringify({
uuid: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
updatedAt: 1486071244,
securityLevel: 'SL5',
contentsHash: 'aaaaaaaa',
title: 'Imported Entry',
location: 'https://www.google.com',
secureContents: {
fields: [
{
value: 'user@test.net',
id: 'email-input',
name: 'email',
type: 'T',
designation: 'username',
},
{
value: 'myservicepassword',
id: 'password-input',
name: 'password',
type: 'P',
designation: 'password',
},
],
sections: [
{
fields: [
{
k: 'concealed',
n: 'AAAAAAAAAAAABBBBBBBBBBBCCCCCCCCC',
v: 'console-password-123',
t: 'console password',
},
],
title: 'Admin Console',
name: 'admin_console',
},
],
passwordHistory: [
{
value: 'old-password',
time: 1447791421,
},
],
const TestData: string =
"***aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee***\n" +
JSON.stringify({
uuid: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
updatedAt: 1486071244,
securityLevel: "SL5",
contentsHash: "aaaaaaaa",
title: "Imported Entry",
location: "https://www.google.com",
secureContents: {
fields: [
{
value: "user@test.net",
id: "email-input",
name: "email",
type: "T",
designation: "username",
},
URLs: [
{
value: "myservicepassword",
id: "password-input",
name: "password",
type: "P",
designation: "password",
},
],
sections: [
{
fields: [
{
label: 'website',
url: 'https://www.google.com',
k: "concealed",
n: "AAAAAAAAAAAABBBBBBBBBBBCCCCCCCCC",
v: "console-password-123",
t: "console password",
},
],
txTimestamp: 1508941334,
createdAt: 1390426636,
typeName: 'webforms.WebForm',
});
],
title: "Admin Console",
name: "admin_console",
},
],
passwordHistory: [
{
value: "old-password",
time: 1447791421,
},
],
},
URLs: [
{
label: "website",
url: "https://www.google.com",
},
],
txTimestamp: 1508941334,
createdAt: 1390426636,
typeName: "webforms.WebForm",
});
const WindowsOpVaultTestData = JSON.stringify({
category: '001',
created: 1544823719,
hmac: 'NtyBmTTPOb88HV3JUKPx1xl/vcMhac9kvCfe/NtszY0=',
k: '**REMOVED LONG LINE FOR LINTER** -Kyle',
tx: 1553395669,
updated: 1553395669,
uuid: '528AB076FB5F4FBF960884B8E01619AC',
overview: {
title: 'Google',
URLs: [
{
u: 'google.com',
},
],
url: 'google.com',
ps: 26,
ainfo: 'googluser',
},
details: {
passwordHistory: [
{
value: 'oldpass1',
time: 1553394449,
},
{
value: 'oldpass2',
time: 1553394457,
},
{
value: 'oldpass3',
time: 1553394458,
},
{
value: 'oldpass4',
time: 1553394459,
},
{
value: 'oldpass5',
time: 1553394460,
},
{
value: 'oldpass6',
time: 1553394461,
},
],
category: "001",
created: 1544823719,
hmac: "NtyBmTTPOb88HV3JUKPx1xl/vcMhac9kvCfe/NtszY0=",
k: "**REMOVED LONG LINE FOR LINTER** -Kyle",
tx: 1553395669,
updated: 1553395669,
uuid: "528AB076FB5F4FBF960884B8E01619AC",
overview: {
title: "Google",
URLs: [
{
u: "google.com",
},
],
url: "google.com",
ps: 26,
ainfo: "googluser",
},
details: {
passwordHistory: [
{
value: "oldpass1",
time: 1553394449,
},
{
value: "oldpass2",
time: 1553394457,
},
{
value: "oldpass3",
time: 1553394458,
},
{
value: "oldpass4",
time: 1553394459,
},
{
value: "oldpass5",
time: 1553394460,
},
{
value: "oldpass6",
time: 1553394461,
},
],
fields: [
{
type: "T",
id: "username",
name: "username",
value: "googluser",
designation: "username",
},
{
type: "P",
id: "password",
name: "password",
value: "12345678901",
designation: "password",
},
],
notesPlain: "This is a note\r\n\r\nline1\r\nline2",
sections: [
{
title: "test",
name: "1214FD88CD30405D9EED14BEB4D61B60",
fields: [
{
type: 'T',
id: 'username',
name: 'username',
value: 'googluser',
designation: 'username',
},
{
type: 'P',
id: 'password',
name: 'password',
value: '12345678901',
designation: 'password',
},
{
k: "string",
n: "6CC3BD77482D4559A4B8BB2D360F821B",
v: "fgfg",
t: "fgggf",
},
{
k: "concealed",
n: "5CFE7BCAA1DF4578BBF7EB508959BFF3",
v: "dfgdfgfdg",
t: "pwfield",
},
],
notesPlain: 'This is a note\r\n\r\nline1\r\nline2',
sections: [
{
title: 'test',
name: '1214FD88CD30405D9EED14BEB4D61B60',
fields: [
{
k: 'string',
n: '6CC3BD77482D4559A4B8BB2D360F821B',
v: 'fgfg',
t: 'fgggf',
},
{
k: 'concealed',
n: '5CFE7BCAA1DF4578BBF7EB508959BFF3',
v: 'dfgdfgfdg',
t: 'pwfield',
},
],
},
],
},
},
],
},
});
const IdentityTestData = JSON.stringify({
uuid: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
updatedAt: 1553365894,
securityLevel: 'SL5',
contentsHash: 'eeeeeeee',
title: 'Test Identity',
secureContents: {
lastname: 'Fritzenberger',
zip: '223344',
birthdate_dd: '11',
homephone: '+49 333 222 111',
company: 'Web Inc.',
firstname: 'Frank',
birthdate_mm: '3',
country: 'de',
sex: 'male',
sections: [
{
fields: [
{
k: 'string',
inputTraits: {
autocapitalization: 'Words',
},
n: 'firstname',
v: 'Frank',
a: {
guarded: 'yes',
},
t: 'first name',
},
{
k: 'string',
inputTraits: {
autocapitalization: 'Words',
},
n: 'initial',
v: 'MD',
a: {
guarded: 'yes',
},
t: 'initial',
},
{
k: 'string',
inputTraits: {
autocapitalization: 'Words',
},
n: 'lastname',
v: 'Fritzenberger',
a: {
guarded: 'yes',
},
t: 'last name',
},
{
k: 'menu',
v: 'male',
n: 'sex',
a: {
guarded: 'yes',
},
t: 'sex',
},
{
k: 'date',
v: 1552305660,
n: 'birthdate',
a: {
guarded: 'yes',
},
t: 'birth date',
},
{
k: 'string',
inputTraits: {
autocapitalization: 'Words',
},
n: 'occupation',
v: 'Engineer',
a: {
guarded: 'yes',
},
t: 'occupation',
},
{
k: 'string',
inputTraits: {
autocapitalization: 'Words',
},
n: 'company',
v: 'Web Inc.',
a: {
guarded: 'yes',
},
t: 'company',
},
{
k: 'string',
inputTraits: {
autocapitalization: 'Words',
},
n: 'department',
v: 'IT',
a: {
guarded: 'yes',
},
t: 'department',
},
{
k: 'string',
inputTraits: {
autocapitalization: 'Words',
},
n: 'jobtitle',
v: 'Developer',
a: {
guarded: 'yes',
},
t: 'job title',
},
],
title: 'Identification',
name: 'name',
uuid: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
updatedAt: 1553365894,
securityLevel: "SL5",
contentsHash: "eeeeeeee",
title: "Test Identity",
secureContents: {
lastname: "Fritzenberger",
zip: "223344",
birthdate_dd: "11",
homephone: "+49 333 222 111",
company: "Web Inc.",
firstname: "Frank",
birthdate_mm: "3",
country: "de",
sex: "male",
sections: [
{
fields: [
{
k: "string",
inputTraits: {
autocapitalization: "Words",
},
{
fields: [
{
k: 'address',
inputTraits: {
autocapitalization: 'Sentences',
},
n: 'address',
v: {
street: 'Mainstreet 1',
city: 'Berlin',
country: 'de',
zip: '223344',
},
a: {
guarded: 'yes',
},
t: 'address',
},
{
k: 'phone',
v: '+49 001 222 333 44',
n: 'defphone',
a: {
guarded: 'yes',
},
t: 'default phone',
},
{
k: 'phone',
v: '+49 333 222 111',
n: 'homephone',
a: {
guarded: 'yes',
},
t: 'home',
},
{
k: 'phone',
n: 'cellphone',
a: {
guarded: 'yes',
},
t: 'mobile',
},
{
k: 'phone',
n: 'busphone',
a: {
guarded: 'yes',
},
t: 'business',
},
],
title: 'Address',
name: 'address',
n: "firstname",
v: "Frank",
a: {
guarded: "yes",
},
{
fields: [
{
k: 'string',
n: 'username',
a: {
guarded: 'yes',
},
t: 'username',
},
{
k: 'string',
n: 'reminderq',
t: 'reminder question',
},
{
k: 'string',
n: 'remindera',
t: 'reminder answer',
},
{
k: 'string',
inputTraits: {
keyboard: 'EmailAddress',
},
n: 'email',
v: 'test@web.de',
a: {
guarded: 'yes',
},
t: 'email',
},
{
k: 'string',
n: 'website',
inputTraits: {
keyboard: 'URL',
},
t: 'website',
},
{
k: 'string',
n: 'icq',
t: 'ICQ',
},
{
k: 'string',
n: 'skype',
t: 'skype',
},
{
k: 'string',
n: 'aim',
t: 'AOL/AIM',
},
{
k: 'string',
n: 'yahoo',
t: 'Yahoo',
},
{
k: 'string',
n: 'msn',
t: 'MSN',
},
{
k: 'string',
n: 'forumsig',
t: 'forum signature',
},
],
title: 'Internet Details',
name: 'internet',
t: "first name",
},
{
k: "string",
inputTraits: {
autocapitalization: "Words",
},
{
title: 'Related Items',
name: 'linked items',
n: "initial",
v: "MD",
a: {
guarded: "yes",
},
t: "initial",
},
{
k: "string",
inputTraits: {
autocapitalization: "Words",
},
n: "lastname",
v: "Fritzenberger",
a: {
guarded: "yes",
},
t: "last name",
},
{
k: "menu",
v: "male",
n: "sex",
a: {
guarded: "yes",
},
t: "sex",
},
{
k: "date",
v: 1552305660,
n: "birthdate",
a: {
guarded: "yes",
},
t: "birth date",
},
{
k: "string",
inputTraits: {
autocapitalization: "Words",
},
n: "occupation",
v: "Engineer",
a: {
guarded: "yes",
},
t: "occupation",
},
{
k: "string",
inputTraits: {
autocapitalization: "Words",
},
n: "company",
v: "Web Inc.",
a: {
guarded: "yes",
},
t: "company",
},
{
k: "string",
inputTraits: {
autocapitalization: "Words",
},
n: "department",
v: "IT",
a: {
guarded: "yes",
},
t: "department",
},
{
k: "string",
inputTraits: {
autocapitalization: "Words",
},
n: "jobtitle",
v: "Developer",
a: {
guarded: "yes",
},
t: "job title",
},
],
initial: 'MD',
address1: 'Mainstreet 1',
city: 'Berlin',
jobtitle: 'Developer',
occupation: 'Engineer',
department: 'IT',
email: 'test@web.de',
birthdate_yy: '2019',
homephone_local: '+49 333 222 111',
defphone_local: '+49 001 222 333 44',
defphone: '+49 001 222 333 44',
},
txTimestamp: 1553365894,
createdAt: 1553364679,
typeName: 'identities.Identity',
title: "Identification",
name: "name",
},
{
fields: [
{
k: "address",
inputTraits: {
autocapitalization: "Sentences",
},
n: "address",
v: {
street: "Mainstreet 1",
city: "Berlin",
country: "de",
zip: "223344",
},
a: {
guarded: "yes",
},
t: "address",
},
{
k: "phone",
v: "+49 001 222 333 44",
n: "defphone",
a: {
guarded: "yes",
},
t: "default phone",
},
{
k: "phone",
v: "+49 333 222 111",
n: "homephone",
a: {
guarded: "yes",
},
t: "home",
},
{
k: "phone",
n: "cellphone",
a: {
guarded: "yes",
},
t: "mobile",
},
{
k: "phone",
n: "busphone",
a: {
guarded: "yes",
},
t: "business",
},
],
title: "Address",
name: "address",
},
{
fields: [
{
k: "string",
n: "username",
a: {
guarded: "yes",
},
t: "username",
},
{
k: "string",
n: "reminderq",
t: "reminder question",
},
{
k: "string",
n: "remindera",
t: "reminder answer",
},
{
k: "string",
inputTraits: {
keyboard: "EmailAddress",
},
n: "email",
v: "test@web.de",
a: {
guarded: "yes",
},
t: "email",
},
{
k: "string",
n: "website",
inputTraits: {
keyboard: "URL",
},
t: "website",
},
{
k: "string",
n: "icq",
t: "ICQ",
},
{
k: "string",
n: "skype",
t: "skype",
},
{
k: "string",
n: "aim",
t: "AOL/AIM",
},
{
k: "string",
n: "yahoo",
t: "Yahoo",
},
{
k: "string",
n: "msn",
t: "MSN",
},
{
k: "string",
n: "forumsig",
t: "forum signature",
},
],
title: "Internet Details",
name: "internet",
},
{
title: "Related Items",
name: "linked items",
},
],
initial: "MD",
address1: "Mainstreet 1",
city: "Berlin",
jobtitle: "Developer",
occupation: "Engineer",
department: "IT",
email: "test@web.de",
birthdate_yy: "2019",
homephone_local: "+49 333 222 111",
defphone_local: "+49 001 222 333 44",
defphone: "+49 001 222 333 44",
},
txTimestamp: 1553365894,
createdAt: 1553364679,
typeName: "identities.Identity",
});
describe('1Password 1Pif Importer', () => {
it('should parse data', async () => {
const importer = new Importer();
const result = await importer.parse(TestData);
expect(result != null).toBe(true);
describe("1Password 1Pif Importer", () => {
it("should parse data", async () => {
const importer = new Importer();
const result = await importer.parse(TestData);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.login.username).toEqual('user@test.net');
expect(cipher.login.password).toEqual('myservicepassword');
expect(cipher.login.uris.length).toEqual(1);
const uriView = cipher.login.uris.shift();
expect(uriView.uri).toEqual('https://www.google.com');
});
const cipher = result.ciphers.shift();
expect(cipher.login.username).toEqual("user@test.net");
expect(cipher.login.password).toEqual("myservicepassword");
expect(cipher.login.uris.length).toEqual(1);
const uriView = cipher.login.uris.shift();
expect(uriView.uri).toEqual("https://www.google.com");
});
it('should create concealed field as "hidden" type', async () => {
const importer = new Importer();
const result = await importer.parse(TestData);
expect(result != null).toBe(true);
it('should create concealed field as "hidden" type', async () => {
const importer = new Importer();
const result = await importer.parse(TestData);
expect(result != null).toBe(true);
const ciphers = result.ciphers;
expect(ciphers.length).toEqual(1);
const ciphers = result.ciphers;
expect(ciphers.length).toEqual(1);
const cipher = ciphers.shift();
const fields = cipher.fields;
expect(fields.length).toEqual(1);
const cipher = ciphers.shift();
const fields = cipher.fields;
expect(fields.length).toEqual(1);
const field = fields.shift();
expect(field.name).toEqual('console password');
expect(field.value).toEqual('console-password-123');
expect(field.type).toEqual(FieldType.Hidden);
});
const field = fields.shift();
expect(field.name).toEqual("console password");
expect(field.value).toEqual("console-password-123");
expect(field.type).toEqual(FieldType.Hidden);
});
it('should create identity records', async () => {
const importer = new Importer();
const result = await importer.parse(IdentityTestData);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.name).toEqual('Test Identity');
it("should create identity records", async () => {
const importer = new Importer();
const result = await importer.parse(IdentityTestData);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.name).toEqual("Test Identity");
const identity = cipher.identity;
expect(identity.firstName).toEqual('Frank');
expect(identity.middleName).toEqual('MD');
expect(identity.lastName).toEqual('Fritzenberger');
expect(identity.company).toEqual('Web Inc.');
expect(identity.address1).toEqual('Mainstreet 1');
expect(identity.country).toEqual('DE');
expect(identity.city).toEqual('Berlin');
expect(identity.postalCode).toEqual('223344');
expect(identity.phone).toEqual('+49 001 222 333 44');
expect(identity.email).toEqual('test@web.de');
const identity = cipher.identity;
expect(identity.firstName).toEqual("Frank");
expect(identity.middleName).toEqual("MD");
expect(identity.lastName).toEqual("Fritzenberger");
expect(identity.company).toEqual("Web Inc.");
expect(identity.address1).toEqual("Mainstreet 1");
expect(identity.country).toEqual("DE");
expect(identity.city).toEqual("Berlin");
expect(identity.postalCode).toEqual("223344");
expect(identity.phone).toEqual("+49 001 222 333 44");
expect(identity.email).toEqual("test@web.de");
// remaining fields as custom fields
expect(cipher.fields.length).toEqual(6);
});
// remaining fields as custom fields
expect(cipher.fields.length).toEqual(6);
});
it('should create password history', async () => {
const importer = new Importer();
const result = await importer.parse(TestData);
const cipher = result.ciphers.shift();
it("should create password history", async () => {
const importer = new Importer();
const result = await importer.parse(TestData);
const cipher = result.ciphers.shift();
expect(cipher.passwordHistory.length).toEqual(1);
const ph = cipher.passwordHistory.shift();
expect(ph.password).toEqual('old-password');
expect(ph.lastUsedDate.toISOString()).toEqual('2015-11-17T20:17:01.000Z');
});
expect(cipher.passwordHistory.length).toEqual(1);
const ph = cipher.passwordHistory.shift();
expect(ph.password).toEqual("old-password");
expect(ph.lastUsedDate.toISOString()).toEqual("2015-11-17T20:17:01.000Z");
});
it('should create password history from windows opvault 1pif format', async () => {
const importer = new Importer();
const result = await importer.parse(WindowsOpVaultTestData);
const cipher = result.ciphers.shift();
it("should create password history from windows opvault 1pif format", async () => {
const importer = new Importer();
const result = await importer.parse(WindowsOpVaultTestData);
const cipher = result.ciphers.shift();
expect(cipher.passwordHistory.length).toEqual(5);
let ph = cipher.passwordHistory.shift();
expect(ph.password).toEqual('oldpass6');
expect(ph.lastUsedDate.toISOString()).toEqual('2019-03-24T02:27:41.000Z');
ph = cipher.passwordHistory.shift();
expect(ph.password).toEqual('oldpass5');
expect(ph.lastUsedDate.toISOString()).toEqual('2019-03-24T02:27:40.000Z');
ph = cipher.passwordHistory.shift();
expect(ph.password).toEqual('oldpass4');
expect(ph.lastUsedDate.toISOString()).toEqual('2019-03-24T02:27:39.000Z');
ph = cipher.passwordHistory.shift();
expect(ph.password).toEqual('oldpass3');
expect(ph.lastUsedDate.toISOString()).toEqual('2019-03-24T02:27:38.000Z');
ph = cipher.passwordHistory.shift();
expect(ph.password).toEqual('oldpass2');
expect(ph.lastUsedDate.toISOString()).toEqual('2019-03-24T02:27:37.000Z');
});
expect(cipher.passwordHistory.length).toEqual(5);
let ph = cipher.passwordHistory.shift();
expect(ph.password).toEqual("oldpass6");
expect(ph.lastUsedDate.toISOString()).toEqual("2019-03-24T02:27:41.000Z");
ph = cipher.passwordHistory.shift();
expect(ph.password).toEqual("oldpass5");
expect(ph.lastUsedDate.toISOString()).toEqual("2019-03-24T02:27:40.000Z");
ph = cipher.passwordHistory.shift();
expect(ph.password).toEqual("oldpass4");
expect(ph.lastUsedDate.toISOString()).toEqual("2019-03-24T02:27:39.000Z");
ph = cipher.passwordHistory.shift();
expect(ph.password).toEqual("oldpass3");
expect(ph.lastUsedDate.toISOString()).toEqual("2019-03-24T02:27:38.000Z");
ph = cipher.passwordHistory.shift();
expect(ph.password).toEqual("oldpass2");
expect(ph.lastUsedDate.toISOString()).toEqual("2019-03-24T02:27:37.000Z");
});
});

View File

@@ -1,71 +1,75 @@
import { OnePasswordMacCsvImporter as Importer } from 'src/importers/onepasswordImporters/onepasswordMacCsvImporter';
import { OnePasswordMacCsvImporter as Importer } from "src/importers/onepasswordImporters/onepasswordMacCsvImporter";
import { CipherType } from 'src/enums/cipherType';
import { CipherView } from 'src/models/view/cipherView';
import { CipherType } from "src/enums/cipherType";
import { CipherView } from "src/models/view/cipherView";
import { data as creditCardData } from './testData/onePasswordCsv/creditCard.mac.csv';
import { data as identityData } from './testData/onePasswordCsv/identity.mac.csv';
import { data as multiTypeData } from './testData/onePasswordCsv/multipleItems.mac.csv';
import { data as creditCardData } from "./testData/onePasswordCsv/creditCard.mac.csv";
import { data as identityData } from "./testData/onePasswordCsv/identity.mac.csv";
import { data as multiTypeData } from "./testData/onePasswordCsv/multipleItems.mac.csv";
function expectIdentity(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.identity).toEqual(jasmine.objectContaining({
firstName: 'first name',
middleName: 'mi',
lastName: 'last name',
username: 'userNam3',
company: 'bitwarden',
phone: '8005555555',
email: 'email@bitwarden.com',
}));
expect(cipher.identity).toEqual(
jasmine.objectContaining({
firstName: "first name",
middleName: "mi",
lastName: "last name",
username: "userNam3",
company: "bitwarden",
phone: "8005555555",
email: "email@bitwarden.com",
})
);
expect(cipher.notes).toContain('address\ncity state zip\nUnited States');
expect(cipher.notes).toContain("address\ncity state zip\nUnited States");
}
function expectCreditCard(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Card);
expect(cipher.type).toBe(CipherType.Card);
expect(cipher.card).toEqual(jasmine.objectContaining({
number: '4111111111111111',
code: '111',
cardholderName: 'test',
expMonth: '1',
expYear: '2030',
}));
expect(cipher.card).toEqual(
jasmine.objectContaining({
number: "4111111111111111",
code: "111",
cardholderName: "test",
expMonth: "1",
expYear: "2030",
})
);
}
describe('1Password mac CSV Importer', () => {
it('should parse identity records', async () => {
const importer = new Importer();
const result = await importer.parse(identityData);
describe("1Password mac CSV Importer", () => {
it("should parse identity records", async () => {
const importer = new Importer();
const result = await importer.parse(identityData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expectIdentity(cipher);
});
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expectIdentity(cipher);
});
it('should parse credit card records', async () => {
const importer = new Importer();
const result = await importer.parse(creditCardData);
it("should parse credit card records", async () => {
const importer = new Importer();
const result = await importer.parse(creditCardData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expectCreditCard(cipher);
});
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expectCreditCard(cipher);
});
it('should parse csv\'s with multiple record type', async () => {
const importer = new Importer();
const result = await importer.parse(multiTypeData);
it("should parse csv's with multiple record type", async () => {
const importer = new Importer();
const result = await importer.parse(multiTypeData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(4);
expectIdentity(result.ciphers[1]);
expectCreditCard(result.ciphers[2]);
});
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(4);
expectIdentity(result.ciphers[1]);
expectCreditCard(result.ciphers[2]);
});
});

View File

@@ -1,82 +1,88 @@
import { OnePasswordWinCsvImporter as Importer } from 'src/importers/onepasswordImporters/onepasswordWinCsvImporter';
import { OnePasswordWinCsvImporter as Importer } from "src/importers/onepasswordImporters/onepasswordWinCsvImporter";
import { CipherType } from 'src/enums/cipherType';
import { FieldType } from 'src/enums/fieldType';
import { CipherView } from 'src/models/view/cipherView';
import { FieldView } from 'src/models/view/fieldView';
import { CipherType } from "src/enums/cipherType";
import { FieldType } from "src/enums/fieldType";
import { CipherView } from "src/models/view/cipherView";
import { FieldView } from "src/models/view/fieldView";
import { data as creditCardData } from './testData/onePasswordCsv/creditCard.windows.csv';
import { data as identityData } from './testData/onePasswordCsv/identity.windows.csv';
import { data as multiTypeData } from './testData/onePasswordCsv/multipleItems.windows.csv';
import { data as creditCardData } from "./testData/onePasswordCsv/creditCard.windows.csv";
import { data as identityData } from "./testData/onePasswordCsv/identity.windows.csv";
import { data as multiTypeData } from "./testData/onePasswordCsv/multipleItems.windows.csv";
function expectIdentity(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.type).toBe(CipherType.Identity);
expect(cipher.identity).toEqual(jasmine.objectContaining({
firstName: 'first name',
middleName: 'mi',
lastName: 'last name',
username: 'userNam3',
company: 'bitwarden',
phone: '8005555555',
email: 'email@bitwarden.com',
}));
expect(cipher.identity).toEqual(
jasmine.objectContaining({
firstName: "first name",
middleName: "mi",
lastName: "last name",
username: "userNam3",
company: "bitwarden",
phone: "8005555555",
email: "email@bitwarden.com",
})
);
expect(cipher.fields).toEqual(jasmine.arrayContaining([
Object.assign(new FieldView(), {
type: FieldType.Text,
name: 'address',
value: 'address city state zip us',
}),
]));
expect(cipher.fields).toEqual(
jasmine.arrayContaining([
Object.assign(new FieldView(), {
type: FieldType.Text,
name: "address",
value: "address city state zip us",
}),
])
);
}
function expectCreditCard(cipher: CipherView) {
expect(cipher.type).toBe(CipherType.Card);
expect(cipher.type).toBe(CipherType.Card);
expect(cipher.card).toEqual(jasmine.objectContaining({
number: '4111111111111111',
code: '111',
cardholderName: 'test',
expMonth: '1',
expYear: '1970',
}));
expect(cipher.card).toEqual(
jasmine.objectContaining({
number: "4111111111111111",
code: "111",
cardholderName: "test",
expMonth: "1",
expYear: "1970",
})
);
}
describe('1Password windows CSV Importer', () => {
let importer: Importer;
beforeEach(() => {
importer = new Importer();
});
describe("1Password windows CSV Importer", () => {
let importer: Importer;
beforeEach(() => {
importer = new Importer();
});
it('should parse identity records', async () => {
const result = await importer.parse(identityData);
it("should parse identity records", async () => {
const result = await importer.parse(identityData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expectIdentity(cipher);
});
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expectIdentity(cipher);
});
it('should parse credit card records', async () => {
const result = await importer.parse(creditCardData);
it("should parse credit card records", async () => {
const result = await importer.parse(creditCardData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expectCreditCard(cipher);
});
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expectCreditCard(cipher);
});
it('should parse csv\'s with multiple record types', async () => {
const result = await importer.parse(multiTypeData);
it("should parse csv's with multiple record types", async () => {
const result = await importer.parse(multiTypeData);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(4);
expect(result).not.toBeNull();
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(4);
expectIdentity(result.ciphers[1]);
expectCreditCard(result.ciphers[2]);
});
expectIdentity(result.ciphers[1]);
expectCreditCard(result.ciphers[2]);
});
});

View File

@@ -1,3 +1,3 @@
export const data = `name,url,username,password,note,cardholdername,cardnumber,cvc,expirydate,zipcode,folder,full_name,phone_number,email,address1,address2,city,country,state
notesFolder,,,,,,,,,,,,,,,,,,
MySuperSecureNoteTitle,,,,MySuperSecureNote,,,,,,notesFolder,,,,,,,,`;
MySuperSecureNoteTitle,,,,MySuperSecureNote,,,,,,notesFolder,,,,,,,,`;

View File

@@ -59,4 +59,4 @@ export const data = `
<label_id>3</label_id>
</card>
</database>
`;
`;

View File

@@ -1,141 +1,127 @@
import { sequentialize } from 'src/misc/sequentialize';
import { sequentialize } from "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);
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);
});
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);
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);
});
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);
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);
});
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", 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 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", 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 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[] = [];
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]);
});
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[] = [];
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]);
});
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;
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) => "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);
});
}
@sequentialize((args) => "baz" + args[0])
baz(a: number): Promise<number> {
this.calls++;
return new Promise((res) => {
setTimeout(() => {
res(a * 3);
}, Math.random() * 100);
});
}
}

View File

@@ -1,110 +1,110 @@
import { sequentialize } from 'src/misc/sequentialize';
import { throttle } from 'src/misc/throttle';
import { sequentialize } from "src/misc/sequentialize";
import { throttle } from "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);
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);
});
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);
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);
});
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);
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);
});
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);
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);
});
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);
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);
});
expect(foo.calls).toBe(5);
});
});
class Foo {
calls = 0;
inflight = 0;
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(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);
});
}
@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);
});
}
@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);
});
}
}

View File

@@ -1,71 +1,73 @@
import { Utils } from 'src/misc/utils';
import { Utils } from "src/misc/utils";
describe('Utils Service', () => {
describe('getDomain', () => {
it('should fail for invalid urls', () => {
expect(Utils.getDomain(null)).toBeNull();
expect(Utils.getDomain(undefined)).toBeNull();
expect(Utils.getDomain(' ')).toBeNull();
expect(Utils.getDomain('https://bit!:"_&ward.com')).toBeNull();
expect(Utils.getDomain('bitwarden')).toBeNull();
});
it('should fail for data urls', () => {
expect(Utils.getDomain('data:image/jpeg;base64,AAA')).toBeNull();
});
it('should handle urls without protocol', () => {
expect(Utils.getDomain('bitwarden.com')).toBe('bitwarden.com');
expect(Utils.getDomain('wrong://bitwarden.com')).toBe('bitwarden.com');
});
it('should handle valid urls', () => {
expect(Utils.getDomain('https://bitwarden')).toBe('bitwarden');
expect(Utils.getDomain('https://bitwarden.com')).toBe('bitwarden.com');
expect(Utils.getDomain('http://bitwarden.com')).toBe('bitwarden.com');
expect(Utils.getDomain('http://vault.bitwarden.com')).toBe('bitwarden.com');
expect(Utils.getDomain('https://user:password@bitwarden.com:8080/password/sites?and&query#hash'))
.toBe('bitwarden.com');
expect(Utils.getDomain('https://bitwarden.unknown')).toBe('bitwarden.unknown');
});
it('should support localhost and IP', () => {
expect(Utils.getDomain('https://localhost')).toBe('localhost');
expect(Utils.getDomain('https://192.168.1.1')).toBe('192.168.1.1');
});
it('should reject invalid hostnames', () => {
expect(Utils.getDomain('https://mywebsite.com$.mywebsite.com')).toBeNull();
expect(Utils.getDomain('https://mywebsite.com!.mywebsite.com')).toBeNull();
});
describe("Utils Service", () => {
describe("getDomain", () => {
it("should fail for invalid urls", () => {
expect(Utils.getDomain(null)).toBeNull();
expect(Utils.getDomain(undefined)).toBeNull();
expect(Utils.getDomain(" ")).toBeNull();
expect(Utils.getDomain('https://bit!:"_&ward.com')).toBeNull();
expect(Utils.getDomain("bitwarden")).toBeNull();
});
describe('getHostname', () => {
it('should fail for invalid urls', () => {
expect(Utils.getHostname(null)).toBeNull();
expect(Utils.getHostname(undefined)).toBeNull();
expect(Utils.getHostname(' ')).toBeNull();
expect(Utils.getHostname('https://bit!:"_&ward.com')).toBeNull();
expect(Utils.getHostname('bitwarden')).toBeNull();
});
it('should handle valid urls', () => {
expect(Utils.getHostname('bitwarden.com')).toBe('bitwarden.com');
expect(Utils.getHostname('https://bitwarden.com')).toBe('bitwarden.com');
expect(Utils.getHostname('http://bitwarden.com')).toBe('bitwarden.com');
expect(Utils.getHostname('http://vault.bitwarden.com')).toBe('vault.bitwarden.com');
});
it('should support localhost and IP', () => {
expect(Utils.getHostname('https://localhost')).toBe('localhost');
expect(Utils.getHostname('https://192.168.1.1')).toBe('192.168.1.1');
});
it("should fail for data urls", () => {
expect(Utils.getDomain("data:image/jpeg;base64,AAA")).toBeNull();
});
describe('newGuid', () => {
it('should create a valid guid', () => {
const validGuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
expect(Utils.newGuid()).toMatch(validGuid);
});
it("should handle urls without protocol", () => {
expect(Utils.getDomain("bitwarden.com")).toBe("bitwarden.com");
expect(Utils.getDomain("wrong://bitwarden.com")).toBe("bitwarden.com");
});
it("should handle valid urls", () => {
expect(Utils.getDomain("https://bitwarden")).toBe("bitwarden");
expect(Utils.getDomain("https://bitwarden.com")).toBe("bitwarden.com");
expect(Utils.getDomain("http://bitwarden.com")).toBe("bitwarden.com");
expect(Utils.getDomain("http://vault.bitwarden.com")).toBe("bitwarden.com");
expect(
Utils.getDomain("https://user:password@bitwarden.com:8080/password/sites?and&query#hash")
).toBe("bitwarden.com");
expect(Utils.getDomain("https://bitwarden.unknown")).toBe("bitwarden.unknown");
});
it("should support localhost and IP", () => {
expect(Utils.getDomain("https://localhost")).toBe("localhost");
expect(Utils.getDomain("https://192.168.1.1")).toBe("192.168.1.1");
});
it("should reject invalid hostnames", () => {
expect(Utils.getDomain("https://mywebsite.com$.mywebsite.com")).toBeNull();
expect(Utils.getDomain("https://mywebsite.com!.mywebsite.com")).toBeNull();
});
});
describe("getHostname", () => {
it("should fail for invalid urls", () => {
expect(Utils.getHostname(null)).toBeNull();
expect(Utils.getHostname(undefined)).toBeNull();
expect(Utils.getHostname(" ")).toBeNull();
expect(Utils.getHostname('https://bit!:"_&ward.com')).toBeNull();
expect(Utils.getHostname("bitwarden")).toBeNull();
});
it("should handle valid urls", () => {
expect(Utils.getHostname("bitwarden.com")).toBe("bitwarden.com");
expect(Utils.getHostname("https://bitwarden.com")).toBe("bitwarden.com");
expect(Utils.getHostname("http://bitwarden.com")).toBe("bitwarden.com");
expect(Utils.getHostname("http://vault.bitwarden.com")).toBe("vault.bitwarden.com");
});
it("should support localhost and IP", () => {
expect(Utils.getHostname("https://localhost")).toBe("localhost");
expect(Utils.getHostname("https://192.168.1.1")).toBe("192.168.1.1");
});
});
describe("newGuid", () => {
it("should create a valid guid", () => {
const validGuid =
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
expect(Utils.newGuid()).toMatch(validGuid);
});
});
});

View File

@@ -1,61 +1,71 @@
import { Arg, Substitute, SubstituteOf } from '@fluffy-spoon/substitute';
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { ApiService } from 'src/abstractions/api.service';
import { CryptoService } from 'src/abstractions/crypto.service';
import { FileUploadService } from 'src/abstractions/fileUpload.service';
import { I18nService } from 'src/abstractions/i18n.service';
import { LogService } from 'src/abstractions/log.service';
import { SearchService } from 'src/abstractions/search.service';
import { SettingsService } from 'src/abstractions/settings.service';
import { StateService } from 'src/abstractions/state.service';
import { ApiService } from "src/abstractions/api.service";
import { CryptoService } from "src/abstractions/crypto.service";
import { FileUploadService } from "src/abstractions/fileUpload.service";
import { I18nService } from "src/abstractions/i18n.service";
import { LogService } from "src/abstractions/log.service";
import { SearchService } from "src/abstractions/search.service";
import { SettingsService } from "src/abstractions/settings.service";
import { StateService } from "src/abstractions/state.service";
import { Utils } from 'src/misc/utils';
import { Cipher } from 'src/models/domain/cipher';
import { EncArrayBuffer } from 'src/models/domain/encArrayBuffer';
import { EncString } from 'src/models/domain/encString';
import { SymmetricCryptoKey } from 'src/models/domain/symmetricCryptoKey';
import { Utils } from "src/misc/utils";
import { Cipher } from "src/models/domain/cipher";
import { EncArrayBuffer } from "src/models/domain/encArrayBuffer";
import { EncString } from "src/models/domain/encString";
import { SymmetricCryptoKey } from "src/models/domain/symmetricCryptoKey";
import { CipherService } from 'src/services/cipher.service';
import { CipherService } from "src/services/cipher.service";
const ENCRYPTED_TEXT = 'This data has been encrypted';
const ENCRYPTED_TEXT = "This data has been encrypted";
const ENCRYPTED_BYTES = new EncArrayBuffer(Utils.fromUtf8ToArray(ENCRYPTED_TEXT).buffer);
describe('Cipher Service', () => {
let cryptoService: SubstituteOf<CryptoService>;
let stateService: SubstituteOf<StateService>;
let settingsService: SubstituteOf<SettingsService>;
let apiService: SubstituteOf<ApiService>;
let fileUploadService: SubstituteOf<FileUploadService>;
let i18nService: SubstituteOf<I18nService>;
let searchService: SubstituteOf<SearchService>;
let logService: SubstituteOf<LogService>;
describe("Cipher Service", () => {
let cryptoService: SubstituteOf<CryptoService>;
let stateService: SubstituteOf<StateService>;
let settingsService: SubstituteOf<SettingsService>;
let apiService: SubstituteOf<ApiService>;
let fileUploadService: SubstituteOf<FileUploadService>;
let i18nService: SubstituteOf<I18nService>;
let searchService: SubstituteOf<SearchService>;
let logService: SubstituteOf<LogService>;
let cipherService: CipherService;
let cipherService: CipherService;
beforeEach(() => {
cryptoService = Substitute.for<CryptoService>();
stateService = Substitute.for<StateService>();
settingsService = Substitute.for<SettingsService>();
apiService = Substitute.for<ApiService>();
fileUploadService = Substitute.for<FileUploadService>();
i18nService = Substitute.for<I18nService>();
searchService = Substitute.for<SearchService>();
logService = Substitute.for<LogService>();
beforeEach(() => {
cryptoService = Substitute.for<CryptoService>();
stateService = Substitute.for<StateService>();
settingsService = Substitute.for<SettingsService>();
apiService = Substitute.for<ApiService>();
fileUploadService = Substitute.for<FileUploadService>();
i18nService = Substitute.for<I18nService>();
searchService = Substitute.for<SearchService>();
logService = Substitute.for<LogService>();
cryptoService.encryptToBytes(Arg.any(), Arg.any()).resolves(ENCRYPTED_BYTES);
cryptoService.encrypt(Arg.any(), Arg.any()).resolves(new EncString(ENCRYPTED_TEXT));
cryptoService.encryptToBytes(Arg.any(), Arg.any()).resolves(ENCRYPTED_BYTES);
cryptoService.encrypt(Arg.any(), Arg.any()).resolves(new EncString(ENCRYPTED_TEXT));
cipherService = new CipherService(cryptoService, settingsService, apiService, fileUploadService,
i18nService, () => searchService, logService, stateService);
});
cipherService = new CipherService(
cryptoService,
settingsService,
apiService,
fileUploadService,
i18nService,
() => searchService,
logService,
stateService
);
});
it('attachments upload encrypted file contents', async () => {
const fileName = 'filename';
const fileData = new Uint8Array(10).buffer;
cryptoService.getOrgKey(Arg.any()).resolves(new SymmetricCryptoKey(new Uint8Array(32).buffer));
it("attachments upload encrypted file contents", async () => {
const fileName = "filename";
const fileData = new Uint8Array(10).buffer;
cryptoService.getOrgKey(Arg.any()).resolves(new SymmetricCryptoKey(new Uint8Array(32).buffer));
await cipherService.saveAttachmentRawWithServer(new Cipher(), fileName, fileData);
await cipherService.saveAttachmentRawWithServer(new Cipher(), fileName, fileData);
fileUploadService.received(1).uploadCipherAttachment(Arg.any(), Arg.any(), new EncString(ENCRYPTED_TEXT), ENCRYPTED_BYTES);
});
fileUploadService
.received(1)
.uploadCipherAttachment(Arg.any(), Arg.any(), new EncString(ENCRYPTED_TEXT), ENCRYPTED_BYTES);
});
});

View File

@@ -1,4 +1,4 @@
import { ConsoleLogService } from 'src/services/consoleLog.service';
import { ConsoleLogService } from "src/services/consoleLog.service";
const originalConsole = console;
let caughtMessage: any;
@@ -6,90 +6,97 @@ let caughtMessage: any;
declare var console: any;
export function interceptConsole(interceptions: any): object {
console = {
// tslint:disable-next-line
log: function () {
interceptions.log = arguments;
},
// tslint:disable-next-line
warn: function () {
interceptions.warn = arguments;
},
// tslint:disable-next-line
error: function () {
interceptions.error = arguments;
},
};
return interceptions;
console = {
// tslint:disable-next-line
log: function () {
interceptions.log = arguments;
},
// tslint:disable-next-line
warn: function () {
interceptions.warn = arguments;
},
// tslint:disable-next-line
error: function () {
interceptions.error = arguments;
},
};
return interceptions;
}
export function restoreConsole() {
console = originalConsole;
console = originalConsole;
}
describe('ConsoleLogService', () => {
let logService: ConsoleLogService;
beforeEach(() => {
caughtMessage = {};
interceptConsole(caughtMessage);
logService = new ConsoleLogService(true);
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, (level) => 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).toEqual({
log: jasmine.arrayWithExactContents(["this is a debug message"]),
});
afterAll(() => {
restoreConsole();
logService.info("this is an info message");
expect(caughtMessage).toEqual({
log: jasmine.arrayWithExactContents(["this is an info message"]),
});
it('filters messages below the set threshold', () => {
logService = new ConsoleLogService(true, level => true);
logService.debug('debug');
logService.info('info');
logService.warning('warning');
logService.error('error');
expect(caughtMessage).toEqual({});
});
it("writes warning messages to console.warn", () => {
logService.warning("this is a warning message");
expect(caughtMessage).toEqual({
warn: jasmine.arrayWithExactContents(["this is a warning message"]),
});
it('only writes debug messages in dev mode', () => {
logService = new ConsoleLogService(false);
logService.debug('debug message');
expect(caughtMessage.log).toBeUndefined();
});
it("writes error messages to console.error", () => {
logService.error("this is an error message");
expect(caughtMessage).toEqual({
error: jasmine.arrayWithExactContents(["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);
it('writes debug/info messages to console.log', () => {
logService.debug('this is a debug message');
expect(caughtMessage).toEqual({ log: jasmine.arrayWithExactContents(['this is a debug message']) });
expect(caughtMessage).toEqual(jasmine.arrayContaining([]));
expect(caughtMessage.log.length).toBe(1);
expect(caughtMessage.log[0]).toEqual(jasmine.stringMatching(/^default: \d+\.?\d*ms$/));
});
logService.info('this is an info message');
expect(caughtMessage).toEqual({ log: jasmine.arrayWithExactContents(['this is an info message']) });
});
it('writes warning messages to console.warn', () => {
logService.warning('this is a warning message');
expect(caughtMessage).toEqual({ warn: jasmine.arrayWithExactContents(['this is a warning message']) });
});
it('writes error messages to console.error', () => {
logService.error('this is an error message');
expect(caughtMessage).toEqual({ error: jasmine.arrayWithExactContents(['this is an error message']) });
});
it("filters time output", async () => {
logService = new ConsoleLogService(true, (level) => true);
logService.time();
logService.timeEnd();
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(jasmine.arrayContaining([]));
expect(caughtMessage.log.length).toBe(1);
expect(caughtMessage.log[0]).toEqual(jasmine.stringMatching(/^default: \d+\.?\d*ms$/));
});
it('filters time output', async () => {
logService = new ConsoleLogService(true, level => true);
logService.time();
logService.timeEnd();
expect(caughtMessage).toEqual({});
});
expect(caughtMessage).toEqual({});
});
});

View File

@@ -1,123 +1,135 @@
import { Substitute, SubstituteOf } from '@fluffy-spoon/substitute';
import { Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { ApiService } from 'src/abstractions/api.service';
import { CipherService } from 'src/abstractions/cipher.service';
import { CryptoService } from 'src/abstractions/crypto.service';
import { FolderService } from 'src/abstractions/folder.service';
import { ApiService } from "src/abstractions/api.service";
import { CipherService } from "src/abstractions/cipher.service";
import { CryptoService } from "src/abstractions/crypto.service";
import { FolderService } from "src/abstractions/folder.service";
import { ExportService } from 'src/services/export.service';
import { ExportService } from "src/services/export.service";
import { Cipher } from 'src/models/domain/cipher';
import { EncString } from 'src/models/domain/encString';
import { Login } from 'src/models/domain/login';
import { CipherWithIds as CipherExport } from 'src/models/export/cipherWithIds';
import { Cipher } from "src/models/domain/cipher";
import { EncString } from "src/models/domain/encString";
import { Login } from "src/models/domain/login";
import { CipherWithIds as CipherExport } from "src/models/export/cipherWithIds";
import { CipherType } from 'src/enums/cipherType';
import { CipherView } from 'src/models/view/cipherView';
import { LoginView } from 'src/models/view/loginView';
import { CipherType } from "src/enums/cipherType";
import { CipherView } from "src/models/view/cipherView";
import { LoginView } from "src/models/view/loginView";
import { BuildTestObject, GetUniqueString } from '../utils';
import { BuildTestObject, GetUniqueString } from "../utils";
const UserCipherViews = [
generateCipherView(false),
generateCipherView(false),
generateCipherView(true),
generateCipherView(false),
generateCipherView(false),
generateCipherView(true),
];
const UserCipherDomains = [
generateCipherDomain(false),
generateCipherDomain(false),
generateCipherDomain(true),
generateCipherDomain(false),
generateCipherDomain(false),
generateCipherDomain(true),
];
function generateCipherView(deleted: boolean) {
return BuildTestObject({
id: GetUniqueString('id'),
notes: GetUniqueString('notes'),
type: CipherType.Login,
login: BuildTestObject<LoginView>({
username: GetUniqueString('username'),
password: GetUniqueString('password'),
}, LoginView),
collectionIds: null,
deletedDate: deleted ? new Date() : null,
}, CipherView);
return BuildTestObject(
{
id: GetUniqueString("id"),
notes: GetUniqueString("notes"),
type: CipherType.Login,
login: BuildTestObject<LoginView>(
{
username: GetUniqueString("username"),
password: GetUniqueString("password"),
},
LoginView
),
collectionIds: null,
deletedDate: deleted ? new Date() : null,
},
CipherView
);
}
function generateCipherDomain(deleted: boolean) {
return BuildTestObject({
id: GetUniqueString('id'),
notes: new EncString(GetUniqueString('notes')),
type: CipherType.Login,
login: BuildTestObject<Login>({
username: new EncString(GetUniqueString('username')),
password: new EncString(GetUniqueString('password')),
}, Login),
collectionIds: null,
deletedDate: deleted ? new Date() : null,
}, Cipher);
return BuildTestObject(
{
id: GetUniqueString("id"),
notes: new EncString(GetUniqueString("notes")),
type: CipherType.Login,
login: BuildTestObject<Login>(
{
username: new EncString(GetUniqueString("username")),
password: new EncString(GetUniqueString("password")),
},
Login
),
collectionIds: null,
deletedDate: deleted ? new Date() : null,
},
Cipher
);
}
function expectEqualCiphers(ciphers: CipherView[] | Cipher[], jsonResult: string) {
const actual = JSON.stringify(JSON.parse(jsonResult).items);
const items: CipherExport[] = [];
ciphers.forEach((c: CipherView | Cipher) => {
const item = new CipherExport();
item.build(c);
items.push(item);
});
const actual = JSON.stringify(JSON.parse(jsonResult).items);
const items: CipherExport[] = [];
ciphers.forEach((c: CipherView | Cipher) => {
const item = new CipherExport();
item.build(c);
items.push(item);
});
expect(actual).toEqual(JSON.stringify(items));
expect(actual).toEqual(JSON.stringify(items));
}
describe('ExportService', () => {
let exportService: ExportService;
let apiService: SubstituteOf<ApiService>;
let cipherService: SubstituteOf<CipherService>;
let folderService: SubstituteOf<FolderService>;
let cryptoService: SubstituteOf<CryptoService>;
describe("ExportService", () => {
let exportService: ExportService;
let apiService: SubstituteOf<ApiService>;
let cipherService: SubstituteOf<CipherService>;
let folderService: SubstituteOf<FolderService>;
let cryptoService: SubstituteOf<CryptoService>;
beforeEach(() => {
apiService = Substitute.for<ApiService>();
cipherService = Substitute.for<CipherService>();
folderService = Substitute.for<FolderService>();
cryptoService = Substitute.for<CryptoService>();
beforeEach(() => {
apiService = Substitute.for<ApiService>();
cipherService = Substitute.for<CipherService>();
folderService = Substitute.for<FolderService>();
cryptoService = Substitute.for<CryptoService>();
folderService.getAllDecrypted().resolves([]);
folderService.getAll().resolves([]);
folderService.getAllDecrypted().resolves([]);
folderService.getAll().resolves([]);
exportService = new ExportService(folderService, cipherService, apiService, cryptoService);
});
exportService = new ExportService(folderService, cipherService, apiService, cryptoService);
});
it('exports unecrypted user ciphers', async () => {
cipherService.getAllDecrypted().resolves(UserCipherViews.slice(0, 1));
it("exports unecrypted user ciphers", async () => {
cipherService.getAllDecrypted().resolves(UserCipherViews.slice(0, 1));
const actual = await exportService.getExport('json');
const actual = await exportService.getExport("json");
expectEqualCiphers(UserCipherViews.slice(0, 1), actual);
});
expectEqualCiphers(UserCipherViews.slice(0, 1), actual);
});
it('exports encrypted json user ciphers', async () => {
cipherService.getAll().resolves(UserCipherDomains.slice(0, 1));
it("exports encrypted json user ciphers", async () => {
cipherService.getAll().resolves(UserCipherDomains.slice(0, 1));
const actual = await exportService.getExport('encrypted_json');
const actual = await exportService.getExport("encrypted_json");
expectEqualCiphers(UserCipherDomains.slice(0, 1), actual);
});
expectEqualCiphers(UserCipherDomains.slice(0, 1), actual);
});
it('does not unecrypted export trashed user items', async () => {
cipherService.getAllDecrypted().resolves(UserCipherViews);
it("does not unecrypted export trashed user items", async () => {
cipherService.getAllDecrypted().resolves(UserCipherViews);
const actual = await exportService.getExport('json');
const actual = await exportService.getExport("json");
expectEqualCiphers(UserCipherViews.slice(0, 2), actual);
});
expectEqualCiphers(UserCipherViews.slice(0, 2), actual);
});
it('does not encrypted export trashed user items', async () => {
cipherService.getAll().resolves(UserCipherDomains);
it("does not encrypted export trashed user items", async () => {
cipherService.getAll().resolves(UserCipherDomains);
const actual = await exportService.getExport('encrypted_json');
const actual = await exportService.getExport("encrypted_json");
expectEqualCiphers(UserCipherDomains.slice(0, 2), actual);
});
expectEqualCiphers(UserCipherDomains.slice(0, 2), actual);
});
});

View File

@@ -1,12 +1,7 @@
{
"spec_dir": "dist/spec",
"spec_files": [
"!web/**/*[sS]pec.js",
"**/*[sS]pec.js"
],
"helpers": [
"helpers.js"
],
"spec_files": ["!web/**/*[sS]pec.js", "**/*[sS]pec.js"],
"helpers": ["helpers.js"],
"stopSpecOnExpectationFailure": false,
"random": true
}

View File

@@ -1,16 +1,19 @@
function newGuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
// tslint:disable:no-bitwise
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
// tslint:disable:no-bitwise
const r = (Math.random() * 16) | 0;
const v = c === "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
export function GetUniqueString(prefix: string = '') {
return prefix + '_' + newGuid();
export function GetUniqueString(prefix: string = "") {
return prefix + "_" + newGuid();
}
export function BuildTestObject<T, K extends keyof T = keyof T>(def: Partial<Pick<T, K>> | T, constructor?: (new () => T)): T {
return Object.assign(constructor === null ? {} : new constructor(), def) as T;
export function BuildTestObject<T, K extends keyof T = keyof T>(
def: Partial<Pick<T, K>> | T,
constructor?: new () => T
): T {
return Object.assign(constructor === null ? {} : new constructor(), def) as T;
}

View File

@@ -1,484 +1,567 @@
import Substitute from '@fluffy-spoon/substitute';
import Substitute from "@fluffy-spoon/substitute";
import { PlatformUtilsService } from 'src/abstractions/platformUtils.service';
import { PlatformUtilsService } from "src/abstractions/platformUtils.service";
import { WebCryptoFunctionService } from 'src/services/webCryptoFunction.service';
import { WebCryptoFunctionService } from "src/services/webCryptoFunction.service";
import { Utils } from 'src/misc/utils';
import { SymmetricCryptoKey } from 'src/models/domain/symmetricCryptoKey';
import { Utils } from "src/misc/utils";
import { SymmetricCryptoKey } from "src/models/domain/symmetricCryptoKey";
const RsaPublicKey = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0Vawl/toXzkEvB82FEtqHP' +
'4xlU2ab/v0crqIfXfIoWF/XXdHGIdrZeilnRXPPJT1B9dTsasttEZNnua/0Rek/cjNDHtzT52irfoZYS7X6HNIfOi54Q+egP' +
'RQ1H7iNHVZz3K8Db9GCSKPeC8MbW6gVCzb15esCe1gGzg6wkMuWYDFYPoh/oBqcIqrGah7firqB1nDedzEjw32heP2DAffVN' +
'084iTDjiWrJNUxBJ2pDD5Z9dT3MzQ2s09ew1yMWK2z37rT3YerC7OgEDmo3WYo3xL3qYJznu3EO2nmrYjiRa40wKSjxsTlUc' +
'xDF+F0uMW8oR9EMUHgepdepfAtLsSAQIDAQAB';
const RsaPrivateKey = 'MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXRVrCX+2hfOQS8Hz' +
'YUS2oc/jGVTZpv+/Ryuoh9d8ihYX9dd0cYh2tl6KWdFc88lPUH11Oxqy20Rk2e5r/RF6T9yM0Me3NPnaKt+hlhLtfoc0h86L' +
'nhD56A9FDUfuI0dVnPcrwNv0YJIo94LwxtbqBULNvXl6wJ7WAbODrCQy5ZgMVg+iH+gGpwiqsZqHt+KuoHWcN53MSPDfaF4/' +
'YMB99U3TziJMOOJask1TEEnakMPln11PczNDazT17DXIxYrbPfutPdh6sLs6AQOajdZijfEvepgnOe7cQ7aeatiOJFrjTApK' +
'PGxOVRzEMX4XS4xbyhH0QxQeB6l16l8C0uxIBAgMBAAECggEASaWfeVDA3cVzOPFSpvJm20OTE+R6uGOU+7vh36TX/POq92q' +
'Buwbd0h0oMD32FxsXywd2IxtBDUSiFM9699qufTVuM0Q3tZw6lHDTOVG08+tPdr8qSbMtw7PGFxN79fHLBxejjO4IrM9lapj' +
'WpxEF+11x7r+wM+0xRZQ8sNFYG46aPfIaty4BGbL0I2DQ2y8I57iBCAy69eht59NLMm27fRWGJIWCuBIjlpfzET1j2HLXUIh' +
'5bTBNzqaN039WH49HczGE3mQKVEJZc/efk3HaVd0a1Sjzyn0QY+N1jtZN3jTRbuDWA1AknkX1LX/0tUhuS3/7C3ejHxjw4Dk' +
'1ZLo5/QKBgQDIWvqFn0+IKRSu6Ua2hDsufIHHUNLelbfLUMmFthxabcUn4zlvIscJO00Tq/ezopSRRvbGiqnxjv/mYxucvOU' +
'BeZtlus0Q9RTACBtw9TGoNTmQbEunJ2FOSlqbQxkBBAjgGEppRPt30iGj/VjAhCATq2MYOa/X4dVR51BqQAFIEwKBgQDBSIf' +
'TFKC/hDk6FKZlgwvupWYJyU9RkyfstPErZFmzoKhPkQ3YORo2oeAYmVUbS9I2iIYpYpYQJHX8jMuCbCz4ONxTCuSIXYQYUcU' +
'q4PglCKp31xBAE6TN8SvhfME9/MvuDssnQinAHuF0GDAhF646T3LLS1not6Vszv7brwSoGwKBgQC88v/8cGfi80ssQZeMnVv' +
'q1UTXIeQcQnoY5lGHJl3K8mbS3TnXE6c9j417Fdz+rj8KWzBzwWXQB5pSPflWcdZO886Xu/mVGmy9RWgLuVFhXwCwsVEPjNX' +
'5ramRb0/vY0yzenUCninBsIxFSbIfrPtLUYCc4hpxr+sr2Mg/y6jpvQKBgBezMRRs3xkcuXepuI2R+BCXL1/b02IJTUf1F+1' +
'eLLGd7YV0H+J3fgNc7gGWK51hOrF9JBZHBGeOUPlaukmPwiPdtQZpu4QNE3l37VlIpKTF30E6mb+BqR+nht3rUjarnMXgAoE' +
'Z18y6/KIjpSMpqC92Nnk/EBM9EYe6Cf4eA9ApAoGAeqEUg46UTlJySkBKURGpIs3v1kkf5I0X8DnOhwb+HPxNaiEdmO7ckm8' +
'+tPVgppLcG0+tMdLjigFQiDUQk2y3WjyxP5ZvXu7U96jaJRI8PFMoE06WeVYcdIzrID2HvqH+w0UQJFrLJ/0Mn4stFAEzXKZ' +
'BokBGnjFnTnKcs7nv/O8=';
const RsaPublicKey =
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0Vawl/toXzkEvB82FEtqHP" +
"4xlU2ab/v0crqIfXfIoWF/XXdHGIdrZeilnRXPPJT1B9dTsasttEZNnua/0Rek/cjNDHtzT52irfoZYS7X6HNIfOi54Q+egP" +
"RQ1H7iNHVZz3K8Db9GCSKPeC8MbW6gVCzb15esCe1gGzg6wkMuWYDFYPoh/oBqcIqrGah7firqB1nDedzEjw32heP2DAffVN" +
"084iTDjiWrJNUxBJ2pDD5Z9dT3MzQ2s09ew1yMWK2z37rT3YerC7OgEDmo3WYo3xL3qYJznu3EO2nmrYjiRa40wKSjxsTlUc" +
"xDF+F0uMW8oR9EMUHgepdepfAtLsSAQIDAQAB";
const RsaPrivateKey =
"MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXRVrCX+2hfOQS8Hz" +
"YUS2oc/jGVTZpv+/Ryuoh9d8ihYX9dd0cYh2tl6KWdFc88lPUH11Oxqy20Rk2e5r/RF6T9yM0Me3NPnaKt+hlhLtfoc0h86L" +
"nhD56A9FDUfuI0dVnPcrwNv0YJIo94LwxtbqBULNvXl6wJ7WAbODrCQy5ZgMVg+iH+gGpwiqsZqHt+KuoHWcN53MSPDfaF4/" +
"YMB99U3TziJMOOJask1TEEnakMPln11PczNDazT17DXIxYrbPfutPdh6sLs6AQOajdZijfEvepgnOe7cQ7aeatiOJFrjTApK" +
"PGxOVRzEMX4XS4xbyhH0QxQeB6l16l8C0uxIBAgMBAAECggEASaWfeVDA3cVzOPFSpvJm20OTE+R6uGOU+7vh36TX/POq92q" +
"Buwbd0h0oMD32FxsXywd2IxtBDUSiFM9699qufTVuM0Q3tZw6lHDTOVG08+tPdr8qSbMtw7PGFxN79fHLBxejjO4IrM9lapj" +
"WpxEF+11x7r+wM+0xRZQ8sNFYG46aPfIaty4BGbL0I2DQ2y8I57iBCAy69eht59NLMm27fRWGJIWCuBIjlpfzET1j2HLXUIh" +
"5bTBNzqaN039WH49HczGE3mQKVEJZc/efk3HaVd0a1Sjzyn0QY+N1jtZN3jTRbuDWA1AknkX1LX/0tUhuS3/7C3ejHxjw4Dk" +
"1ZLo5/QKBgQDIWvqFn0+IKRSu6Ua2hDsufIHHUNLelbfLUMmFthxabcUn4zlvIscJO00Tq/ezopSRRvbGiqnxjv/mYxucvOU" +
"BeZtlus0Q9RTACBtw9TGoNTmQbEunJ2FOSlqbQxkBBAjgGEppRPt30iGj/VjAhCATq2MYOa/X4dVR51BqQAFIEwKBgQDBSIf" +
"TFKC/hDk6FKZlgwvupWYJyU9RkyfstPErZFmzoKhPkQ3YORo2oeAYmVUbS9I2iIYpYpYQJHX8jMuCbCz4ONxTCuSIXYQYUcU" +
"q4PglCKp31xBAE6TN8SvhfME9/MvuDssnQinAHuF0GDAhF646T3LLS1not6Vszv7brwSoGwKBgQC88v/8cGfi80ssQZeMnVv" +
"q1UTXIeQcQnoY5lGHJl3K8mbS3TnXE6c9j417Fdz+rj8KWzBzwWXQB5pSPflWcdZO886Xu/mVGmy9RWgLuVFhXwCwsVEPjNX" +
"5ramRb0/vY0yzenUCninBsIxFSbIfrPtLUYCc4hpxr+sr2Mg/y6jpvQKBgBezMRRs3xkcuXepuI2R+BCXL1/b02IJTUf1F+1" +
"eLLGd7YV0H+J3fgNc7gGWK51hOrF9JBZHBGeOUPlaukmPwiPdtQZpu4QNE3l37VlIpKTF30E6mb+BqR+nht3rUjarnMXgAoE" +
"Z18y6/KIjpSMpqC92Nnk/EBM9EYe6Cf4eA9ApAoGAeqEUg46UTlJySkBKURGpIs3v1kkf5I0X8DnOhwb+HPxNaiEdmO7ckm8" +
"+tPVgppLcG0+tMdLjigFQiDUQk2y3WjyxP5ZvXu7U96jaJRI8PFMoE06WeVYcdIzrID2HvqH+w0UQJFrLJ/0Mn4stFAEzXKZ" +
"BokBGnjFnTnKcs7nv/O8=";
const Sha1Mac = '4d4c223f95dc577b665ec4ccbcb680b80a397038';
const Sha256Mac = '6be3caa84922e12aaaaa2f16c40d44433bb081ef323db584eb616333ab4e874f';
const Sha512Mac = '21910e341fa12106ca35758a2285374509326c9fbe0bd64e7b99c898f841dc948c58ce66d3504d8883c' +
'5ea7817a0b7c5d4d9b00364ccd214669131fc17fe4aca';
const Sha1Mac = "4d4c223f95dc577b665ec4ccbcb680b80a397038";
const Sha256Mac = "6be3caa84922e12aaaaa2f16c40d44433bb081ef323db584eb616333ab4e874f";
const Sha512Mac =
"21910e341fa12106ca35758a2285374509326c9fbe0bd64e7b99c898f841dc948c58ce66d3504d8883c" +
"5ea7817a0b7c5d4d9b00364ccd214669131fc17fe4aca";
describe('WebCrypto Function Service', () => {
describe('pbkdf2', () => {
const regular256Key = 'pj9prw/OHPleXI6bRdmlaD+saJS4awrMiQsQiDjeu2I=';
const utf8256Key = 'yqvoFXgMRmHR3QPYr5pyR4uVuoHkltv9aHUP63p8n7I=';
const unicode256Key = 'ZdeOata6xoRpB4DLp8zHhXz5kLmkWtX5pd+TdRH8w8w=';
describe("WebCrypto Function Service", () => {
describe("pbkdf2", () => {
const regular256Key = "pj9prw/OHPleXI6bRdmlaD+saJS4awrMiQsQiDjeu2I=";
const utf8256Key = "yqvoFXgMRmHR3QPYr5pyR4uVuoHkltv9aHUP63p8n7I=";
const unicode256Key = "ZdeOata6xoRpB4DLp8zHhXz5kLmkWtX5pd+TdRH8w8w=";
const regular512Key = 'liTi/Ke8LPU1Qv+Vl7NGEVt/XMbsBVJ2kQxtVG/Z1/JFHFKQW3ZkI81qVlwTiCpb+cFXzs+57' +
'eyhhx5wfKo5Cg==';
const utf8512Key = 'df0KdvIBeCzD/kyXptwQohaqUa4e7IyFUyhFQjXCANu5T+scq55hCcE4dG4T/MhAk2exw8j7ixRN' +
'zXANiVZpnw==';
const unicode512Key = 'FE+AnUJaxv8jh+zUDtZz4mjjcYk0/PZDZm+SLJe3XtxtnpdqqpblX6JjuMZt/dYYNMOrb2+mD' +
'L3FiQDTROh1lg==';
const regular512Key =
"liTi/Ke8LPU1Qv+Vl7NGEVt/XMbsBVJ2kQxtVG/Z1/JFHFKQW3ZkI81qVlwTiCpb+cFXzs+57" +
"eyhhx5wfKo5Cg==";
const utf8512Key =
"df0KdvIBeCzD/kyXptwQohaqUa4e7IyFUyhFQjXCANu5T+scq55hCcE4dG4T/MhAk2exw8j7ixRN" +
"zXANiVZpnw==";
const unicode512Key =
"FE+AnUJaxv8jh+zUDtZz4mjjcYk0/PZDZm+SLJe3XtxtnpdqqpblX6JjuMZt/dYYNMOrb2+mD" +
"L3FiQDTROh1lg==";
testPbkdf2('sha256', regular256Key, utf8256Key, unicode256Key);
testPbkdf2('sha512', regular512Key, utf8512Key, unicode512Key);
testPbkdf2("sha256", regular256Key, utf8256Key, unicode256Key);
testPbkdf2("sha512", regular512Key, utf8512Key, unicode512Key);
});
describe("hkdf", () => {
const regular256Key = "qBUmEYtwTwwGPuw/z6bs/qYXXYNUlocFlyAuuANI8Pw=";
const utf8256Key = "6DfJwW1R3txgiZKkIFTvVAb7qVlG7lKcmJGJoxR2GBU=";
const unicode256Key = "gejGI82xthA+nKtKmIh82kjw+ttHr+ODsUoGdu5sf0A=";
const regular512Key = "xe5cIG6ZfwGmb1FvsOedM0XKOm21myZkjL/eDeKIqqM=";
const utf8512Key = "XQMVBnxVEhlvjSFDQc77j5GDE9aorvbS0vKnjhRg0LY=";
const unicode512Key = "148GImrTbrjaGAe/iWEpclINM8Ehhko+9lB14+52lqc=";
testHkdf("sha256", regular256Key, utf8256Key, unicode256Key);
testHkdf("sha512", regular512Key, utf8512Key, unicode512Key);
});
describe("hkdfExpand", () => {
const prk16Byte = "criAmKtfzxanbgea5/kelQ==";
const prk32Byte = "F5h4KdYQnIVH4rKH0P9CZb1GrR4n16/sJrS0PsQEn0Y=";
const prk64Byte =
"ssBK0mRG17VHdtsgt8yo4v25CRNpauH+0r2fwY/E9rLyaFBAOMbIeTry+" +
"gUJ28p8y+hFh3EI9pcrEWaNvFYonQ==";
testHkdfExpand("sha256", prk32Byte, 32, "BnIqJlfnHm0e/2iB/15cbHyR19ARPIcWRp4oNS22CD8=");
testHkdfExpand(
"sha256",
prk32Byte,
64,
"BnIqJlfnHm0e/2iB/15cbHyR19ARPIcWRp4oNS22CD9BV+" +
"/queOZenPNkDhmlVyL2WZ3OSU5+7ISNF5NhNfvZA=="
);
testHkdfExpand("sha512", prk64Byte, 32, "uLWbMWodSBms5uGJ5WTRTesyW+MD7nlpCZvagvIRXlk=");
testHkdfExpand(
"sha512",
prk64Byte,
64,
"uLWbMWodSBms5uGJ5WTRTesyW+MD7nlpCZvagvIRXlkY5Pv0sB+" +
"MqvaopmkC6sD/j89zDwTV9Ib2fpucUydO8w=="
);
it("should fail with prk too small", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const f = cryptoFunctionService.hkdfExpand(
Utils.fromB64ToArray(prk16Byte),
"info",
32,
"sha256"
);
await expectAsync(f).toBeRejectedWith(new Error("prk is too small."));
});
describe('hkdf', () => {
const regular256Key = 'qBUmEYtwTwwGPuw/z6bs/qYXXYNUlocFlyAuuANI8Pw=';
const utf8256Key = '6DfJwW1R3txgiZKkIFTvVAb7qVlG7lKcmJGJoxR2GBU=';
const unicode256Key = 'gejGI82xthA+nKtKmIh82kjw+ttHr+ODsUoGdu5sf0A=';
it("should fail with outputByteSize is too large", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const f = cryptoFunctionService.hkdfExpand(
Utils.fromB64ToArray(prk32Byte),
"info",
8161,
"sha256"
);
await expectAsync(f).toBeRejectedWith(new Error("outputByteSize is too large."));
});
});
const regular512Key = 'xe5cIG6ZfwGmb1FvsOedM0XKOm21myZkjL/eDeKIqqM=';
const utf8512Key = 'XQMVBnxVEhlvjSFDQc77j5GDE9aorvbS0vKnjhRg0LY=';
const unicode512Key = '148GImrTbrjaGAe/iWEpclINM8Ehhko+9lB14+52lqc=';
describe("hash", () => {
const regular1Hash = "2a241604fb921fad12bf877282457268e1dccb70";
const utf81Hash = "85672798dc5831e96d6c48655d3d39365a9c88b6";
const unicode1Hash = "39c975935054a3efc805a9709b60763a823a6ad4";
testHkdf('sha256', regular256Key, utf8256Key, unicode256Key);
testHkdf('sha512', regular512Key, utf8512Key, unicode512Key);
const regular256Hash = "2b8e96031d352a8655d733d7a930b5ffbea69dc25cf65c7bca7dd946278908b2";
const utf8256Hash = "25fe8440f5b01ed113b0a0e38e721b126d2f3f77a67518c4a04fcde4e33eeb9d";
const unicode256Hash = "adc1c0c2afd6e92cefdf703f9b6eb2c38e0d6d1a040c83f8505c561fea58852e";
const regular512Hash =
"c15cf11d43bde333647e3f559ec4193bb2edeaa0e8b902772f514cdf3f785a3f49a6e02a4b87b3" +
"b47523271ad45b7e0aebb5cdcc1bc54815d256eb5dcb80da9d";
const utf8512Hash =
"035c31a877a291af09ed2d3a1a293e69c3e079ea2cecc00211f35e6bce10474ca3ad6e30b59e26118" +
"37463f20969c5bc95282965a051a88f8cdf2e166549fcdd";
const unicode512Hash =
"2b16a5561af8ad6fe414cc103fc8036492e1fc6d9aabe1b655497054f760fe0e34c5d100ac773d" +
"9f3030438284f22dbfa20cb2e9b019f2c98dfe38ce1ef41bae";
const regularMd5 = "5eceffa53a5fd58c44134211e2c5f522";
const utf8Md5 = "3abc9433c09551b939c80aa0aa3174e1";
const unicodeMd5 = "85ae134072c8d81257933f7045ba17ca";
testHash("sha1", regular1Hash, utf81Hash, unicode1Hash);
testHash("sha256", regular256Hash, utf8256Hash, unicode256Hash);
testHash("sha512", regular512Hash, utf8512Hash, unicode512Hash);
testHash("md5", regularMd5, utf8Md5, unicodeMd5);
});
describe("hmac", () => {
testHmac("sha1", Sha1Mac);
testHmac("sha256", Sha256Mac);
testHmac("sha512", Sha512Mac);
});
describe("compare", () => {
it("should successfully compare two of the same values", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const a = new Uint8Array(2);
a[0] = 1;
a[1] = 2;
const equal = await cryptoFunctionService.compare(a.buffer, a.buffer);
expect(equal).toBe(true);
});
describe('hkdfExpand', () => {
const prk16Byte = 'criAmKtfzxanbgea5/kelQ==';
const prk32Byte = 'F5h4KdYQnIVH4rKH0P9CZb1GrR4n16/sJrS0PsQEn0Y=';
const prk64Byte = 'ssBK0mRG17VHdtsgt8yo4v25CRNpauH+0r2fwY/E9rLyaFBAOMbIeTry+' +
'gUJ28p8y+hFh3EI9pcrEWaNvFYonQ==';
testHkdfExpand('sha256', prk32Byte, 32, 'BnIqJlfnHm0e/2iB/15cbHyR19ARPIcWRp4oNS22CD8=');
testHkdfExpand('sha256', prk32Byte, 64, 'BnIqJlfnHm0e/2iB/15cbHyR19ARPIcWRp4oNS22CD9BV+' +
'/queOZenPNkDhmlVyL2WZ3OSU5+7ISNF5NhNfvZA==');
testHkdfExpand('sha512', prk64Byte, 32, 'uLWbMWodSBms5uGJ5WTRTesyW+MD7nlpCZvagvIRXlk=');
testHkdfExpand('sha512', prk64Byte, 64, 'uLWbMWodSBms5uGJ5WTRTesyW+MD7nlpCZvagvIRXlkY5Pv0sB+' +
'MqvaopmkC6sD/j89zDwTV9Ib2fpucUydO8w==');
it('should fail with prk too small', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const f = cryptoFunctionService.hkdfExpand(Utils.fromB64ToArray(prk16Byte), 'info', 32, 'sha256');
await expectAsync(f).toBeRejectedWith(new Error('prk is too small.'));
});
it('should fail with outputByteSize is too large', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const f = cryptoFunctionService.hkdfExpand(Utils.fromB64ToArray(prk32Byte), 'info', 8161, 'sha256');
await expectAsync(f).toBeRejectedWith(new Error('outputByteSize is too large.'));
});
it("should successfully compare two different values of the same length", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const a = new Uint8Array(2);
a[0] = 1;
a[1] = 2;
const b = new Uint8Array(2);
b[0] = 3;
b[1] = 4;
const equal = await cryptoFunctionService.compare(a.buffer, b.buffer);
expect(equal).toBe(false);
});
describe('hash', () => {
const regular1Hash = '2a241604fb921fad12bf877282457268e1dccb70';
const utf81Hash = '85672798dc5831e96d6c48655d3d39365a9c88b6';
const unicode1Hash = '39c975935054a3efc805a9709b60763a823a6ad4';
it("should successfully compare two different values of different lengths", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const a = new Uint8Array(2);
a[0] = 1;
a[1] = 2;
const b = new Uint8Array(2);
b[0] = 3;
const equal = await cryptoFunctionService.compare(a.buffer, b.buffer);
expect(equal).toBe(false);
});
});
const regular256Hash = '2b8e96031d352a8655d733d7a930b5ffbea69dc25cf65c7bca7dd946278908b2';
const utf8256Hash = '25fe8440f5b01ed113b0a0e38e721b126d2f3f77a67518c4a04fcde4e33eeb9d';
const unicode256Hash = 'adc1c0c2afd6e92cefdf703f9b6eb2c38e0d6d1a040c83f8505c561fea58852e';
describe("hmacFast", () => {
testHmacFast("sha1", Sha1Mac);
testHmacFast("sha256", Sha256Mac);
testHmacFast("sha512", Sha512Mac);
});
const regular512Hash = 'c15cf11d43bde333647e3f559ec4193bb2edeaa0e8b902772f514cdf3f785a3f49a6e02a4b87b3' +
'b47523271ad45b7e0aebb5cdcc1bc54815d256eb5dcb80da9d';
const utf8512Hash = '035c31a877a291af09ed2d3a1a293e69c3e079ea2cecc00211f35e6bce10474ca3ad6e30b59e26118' +
'37463f20969c5bc95282965a051a88f8cdf2e166549fcdd';
const unicode512Hash = '2b16a5561af8ad6fe414cc103fc8036492e1fc6d9aabe1b655497054f760fe0e34c5d100ac773d' +
'9f3030438284f22dbfa20cb2e9b019f2c98dfe38ce1ef41bae';
const regularMd5 = '5eceffa53a5fd58c44134211e2c5f522';
const utf8Md5 = '3abc9433c09551b939c80aa0aa3174e1';
const unicodeMd5 = '85ae134072c8d81257933f7045ba17ca';
testHash('sha1', regular1Hash, utf81Hash, unicode1Hash);
testHash('sha256', regular256Hash, utf8256Hash, unicode256Hash);
testHash('sha512', regular512Hash, utf8512Hash, unicode512Hash);
testHash('md5', regularMd5, utf8Md5, unicodeMd5);
describe("compareFast", () => {
it("should successfully compare two of the same values", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const a = new Uint8Array(2);
a[0] = 1;
a[1] = 2;
const aByteString = Utils.fromBufferToByteString(a.buffer);
const equal = await cryptoFunctionService.compareFast(aByteString, aByteString);
expect(equal).toBe(true);
});
describe('hmac', () => {
testHmac('sha1', Sha1Mac);
testHmac('sha256', Sha256Mac);
testHmac('sha512', Sha512Mac);
it("should successfully compare two different values of the same length", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const a = new Uint8Array(2);
a[0] = 1;
a[1] = 2;
const aByteString = Utils.fromBufferToByteString(a.buffer);
const b = new Uint8Array(2);
b[0] = 3;
b[1] = 4;
const bByteString = Utils.fromBufferToByteString(b.buffer);
const equal = await cryptoFunctionService.compareFast(aByteString, bByteString);
expect(equal).toBe(false);
});
describe('compare', () => {
it('should successfully compare two of the same values', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const a = new Uint8Array(2);
a[0] = 1;
a[1] = 2;
const equal = await cryptoFunctionService.compare(a.buffer, a.buffer);
expect(equal).toBe(true);
});
it("should successfully compare two different values of different lengths", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const a = new Uint8Array(2);
a[0] = 1;
a[1] = 2;
const aByteString = Utils.fromBufferToByteString(a.buffer);
const b = new Uint8Array(2);
b[0] = 3;
const bByteString = Utils.fromBufferToByteString(b.buffer);
const equal = await cryptoFunctionService.compareFast(aByteString, bByteString);
expect(equal).toBe(false);
});
});
it('should successfully compare two different values of the same length', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const a = new Uint8Array(2);
a[0] = 1;
a[1] = 2;
const b = new Uint8Array(2);
b[0] = 3;
b[1] = 4;
const equal = await cryptoFunctionService.compare(a.buffer, b.buffer);
expect(equal).toBe(false);
});
it('should successfully compare two different values of different lengths', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const a = new Uint8Array(2);
a[0] = 1;
a[1] = 2;
const b = new Uint8Array(2);
b[0] = 3;
const equal = await cryptoFunctionService.compare(a.buffer, b.buffer);
expect(equal).toBe(false);
});
describe("aesEncrypt", () => {
it("should successfully encrypt data", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const iv = makeStaticByteArray(16);
const key = makeStaticByteArray(32);
const data = Utils.fromUtf8ToArray("EncryptMe!");
const encValue = await cryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer);
expect(Utils.fromBufferToB64(encValue)).toBe("ByUF8vhyX4ddU9gcooznwA==");
});
describe('hmacFast', () => {
testHmacFast('sha1', Sha1Mac);
testHmacFast('sha256', Sha256Mac);
testHmacFast('sha512', Sha512Mac);
it("should successfully encrypt and then decrypt data fast", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const iv = makeStaticByteArray(16);
const key = makeStaticByteArray(32);
const value = "EncryptMe!";
const data = Utils.fromUtf8ToArray(value);
const encValue = await cryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer);
const encData = Utils.fromBufferToB64(encValue);
const b64Iv = Utils.fromBufferToB64(iv.buffer);
const symKey = new SymmetricCryptoKey(key.buffer);
const params = cryptoFunctionService.aesDecryptFastParameters(encData, b64Iv, null, symKey);
const decValue = await cryptoFunctionService.aesDecryptFast(params);
expect(decValue).toBe(value);
});
describe('compareFast', () => {
it('should successfully compare two of the same values', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const a = new Uint8Array(2);
a[0] = 1;
a[1] = 2;
const aByteString = Utils.fromBufferToByteString(a.buffer);
const equal = await cryptoFunctionService.compareFast(aByteString, aByteString);
expect(equal).toBe(true);
});
it("should successfully encrypt and then decrypt data", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const iv = makeStaticByteArray(16);
const key = makeStaticByteArray(32);
const value = "EncryptMe!";
const data = Utils.fromUtf8ToArray(value);
const encValue = await cryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer);
const decValue = await cryptoFunctionService.aesDecrypt(encValue, iv.buffer, key.buffer);
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
});
});
it('should successfully compare two different values of the same length', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const a = new Uint8Array(2);
a[0] = 1;
a[1] = 2;
const aByteString = Utils.fromBufferToByteString(a.buffer);
const b = new Uint8Array(2);
b[0] = 3;
b[1] = 4;
const bByteString = Utils.fromBufferToByteString(b.buffer);
const equal = await cryptoFunctionService.compareFast(aByteString, bByteString);
expect(equal).toBe(false);
});
describe("aesDecryptFast", () => {
it("should successfully decrypt data", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const iv = Utils.fromBufferToB64(makeStaticByteArray(16).buffer);
const symKey = new SymmetricCryptoKey(makeStaticByteArray(32).buffer);
const data = "ByUF8vhyX4ddU9gcooznwA==";
const params = cryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey);
const decValue = await cryptoFunctionService.aesDecryptFast(params);
expect(decValue).toBe("EncryptMe!");
});
});
it('should successfully compare two different values of different lengths', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const a = new Uint8Array(2);
a[0] = 1;
a[1] = 2;
const aByteString = Utils.fromBufferToByteString(a.buffer);
const b = new Uint8Array(2);
b[0] = 3;
const bByteString = Utils.fromBufferToByteString(b.buffer);
const equal = await cryptoFunctionService.compareFast(aByteString, bByteString);
expect(equal).toBe(false);
});
describe("aesDecrypt", () => {
it("should successfully decrypt data", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const iv = makeStaticByteArray(16);
const key = makeStaticByteArray(32);
const data = Utils.fromB64ToArray("ByUF8vhyX4ddU9gcooznwA==");
const decValue = await cryptoFunctionService.aesDecrypt(data.buffer, iv.buffer, key.buffer);
expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!");
});
});
describe("rsaEncrypt", () => {
it("should successfully encrypt and then decrypt data", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const pubKey = Utils.fromB64ToArray(RsaPublicKey);
const privKey = Utils.fromB64ToArray(RsaPrivateKey);
const value = "EncryptMe!";
const data = Utils.fromUtf8ToArray(value);
const encValue = await cryptoFunctionService.rsaEncrypt(data.buffer, pubKey.buffer, "sha1");
const decValue = await cryptoFunctionService.rsaDecrypt(encValue, privKey.buffer, "sha1");
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
});
});
describe("rsaDecrypt", () => {
it("should successfully decrypt data", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const privKey = Utils.fromB64ToArray(RsaPrivateKey);
const data = Utils.fromB64ToArray(
"A1/p8BQzN9UrbdYxUY2Va5+kPLyfZXF9JsZrjeEXcaclsnHurdxVAJcnbEqYMP3UXV" +
"4YAS/mpf+Rxe6/X0WS1boQdA0MAHSgx95hIlAraZYpiMLLiJRKeo2u8YivCdTM9V5vuAEJwf9Tof/qFsFci3sApdbATkorCT" +
"zFOIEPF2S1zgperEP23M01mr4dWVdYN18B32YF67xdJHMbFhp5dkQwv9CmscoWq7OE5HIfOb+JAh7BEZb+CmKhM3yWJvoR/D" +
"/5jcercUtK2o+XrzNrL4UQ7yLZcFz6Bfwb/j6ICYvqd/YJwXNE6dwlL57OfwJyCdw2rRYf0/qI00t9u8Iitw=="
);
const decValue = await cryptoFunctionService.rsaDecrypt(data.buffer, privKey.buffer, "sha1");
expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!");
});
});
describe("rsaExtractPublicKey", () => {
it("should successfully extract key", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const privKey = Utils.fromB64ToArray(RsaPrivateKey);
const publicKey = await cryptoFunctionService.rsaExtractPublicKey(privKey.buffer);
expect(Utils.fromBufferToB64(publicKey)).toBe(RsaPublicKey);
});
});
describe("rsaGenerateKeyPair", () => {
testRsaGenerateKeyPair(1024);
testRsaGenerateKeyPair(2048);
// Generating 4096 bit keys can be slow. Commenting it out to save CI.
// testRsaGenerateKeyPair(4096);
});
describe("randomBytes", () => {
it("should make a value of the correct length", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const randomData = await cryptoFunctionService.randomBytes(16);
expect(randomData.byteLength).toBe(16);
});
describe('aesEncrypt', () => {
it('should successfully encrypt data', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const iv = makeStaticByteArray(16);
const key = makeStaticByteArray(32);
const data = Utils.fromUtf8ToArray('EncryptMe!');
const encValue = await cryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer);
expect(Utils.fromBufferToB64(encValue)).toBe('ByUF8vhyX4ddU9gcooznwA==');
});
it('should successfully encrypt and then decrypt data fast', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const iv = makeStaticByteArray(16);
const key = makeStaticByteArray(32);
const value = 'EncryptMe!';
const data = Utils.fromUtf8ToArray(value);
const encValue = await cryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer);
const encData = Utils.fromBufferToB64(encValue);
const b64Iv = Utils.fromBufferToB64(iv.buffer);
const symKey = new SymmetricCryptoKey(key.buffer);
const params = cryptoFunctionService.aesDecryptFastParameters(encData, b64Iv, null, symKey);
const decValue = await cryptoFunctionService.aesDecryptFast(params);
expect(decValue).toBe(value);
});
it('should successfully encrypt and then decrypt data', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const iv = makeStaticByteArray(16);
const key = makeStaticByteArray(32);
const value = 'EncryptMe!';
const data = Utils.fromUtf8ToArray(value);
const encValue = await cryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer);
const decValue = await cryptoFunctionService.aesDecrypt(encValue, iv.buffer, key.buffer);
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
});
});
describe('aesDecryptFast', () => {
it('should successfully decrypt data', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const iv = Utils.fromBufferToB64(makeStaticByteArray(16).buffer);
const symKey = new SymmetricCryptoKey(makeStaticByteArray(32).buffer);
const data = 'ByUF8vhyX4ddU9gcooznwA==';
const params = cryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey);
const decValue = await cryptoFunctionService.aesDecryptFast(params);
expect(decValue).toBe('EncryptMe!');
});
});
describe('aesDecrypt', () => {
it('should successfully decrypt data', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const iv = makeStaticByteArray(16);
const key = makeStaticByteArray(32);
const data = Utils.fromB64ToArray('ByUF8vhyX4ddU9gcooznwA==');
const decValue = await cryptoFunctionService.aesDecrypt(data.buffer, iv.buffer, key.buffer);
expect(Utils.fromBufferToUtf8(decValue)).toBe('EncryptMe!');
});
});
describe('rsaEncrypt', () => {
it('should successfully encrypt and then decrypt data', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const pubKey = Utils.fromB64ToArray(RsaPublicKey);
const privKey = Utils.fromB64ToArray(RsaPrivateKey);
const value = 'EncryptMe!';
const data = Utils.fromUtf8ToArray(value);
const encValue = await cryptoFunctionService.rsaEncrypt(data.buffer, pubKey.buffer, 'sha1');
const decValue = await cryptoFunctionService.rsaDecrypt(encValue, privKey.buffer, 'sha1');
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
});
});
describe('rsaDecrypt', () => {
it('should successfully decrypt data', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const privKey = Utils.fromB64ToArray(RsaPrivateKey);
const data = Utils.fromB64ToArray('A1/p8BQzN9UrbdYxUY2Va5+kPLyfZXF9JsZrjeEXcaclsnHurdxVAJcnbEqYMP3UXV' +
'4YAS/mpf+Rxe6/X0WS1boQdA0MAHSgx95hIlAraZYpiMLLiJRKeo2u8YivCdTM9V5vuAEJwf9Tof/qFsFci3sApdbATkorCT' +
'zFOIEPF2S1zgperEP23M01mr4dWVdYN18B32YF67xdJHMbFhp5dkQwv9CmscoWq7OE5HIfOb+JAh7BEZb+CmKhM3yWJvoR/D' +
'/5jcercUtK2o+XrzNrL4UQ7yLZcFz6Bfwb/j6ICYvqd/YJwXNE6dwlL57OfwJyCdw2rRYf0/qI00t9u8Iitw==');
const decValue = await cryptoFunctionService.rsaDecrypt(data.buffer, privKey.buffer, 'sha1');
expect(Utils.fromBufferToUtf8(decValue)).toBe('EncryptMe!');
});
});
describe('rsaExtractPublicKey', () => {
it('should successfully extract key', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const privKey = Utils.fromB64ToArray(RsaPrivateKey);
const publicKey = await cryptoFunctionService.rsaExtractPublicKey(privKey.buffer);
expect(Utils.fromBufferToB64(publicKey)).toBe(RsaPublicKey);
});
});
describe('rsaGenerateKeyPair', () => {
testRsaGenerateKeyPair(1024);
testRsaGenerateKeyPair(2048);
// Generating 4096 bit keys can be slow. Commenting it out to save CI.
// testRsaGenerateKeyPair(4096);
});
describe('randomBytes', () => {
it('should make a value of the correct length', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const randomData = await cryptoFunctionService.randomBytes(16);
expect(randomData.byteLength).toBe(16);
});
it('should not make the same value twice', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const randomData = await cryptoFunctionService.randomBytes(16);
const randomData2 = await cryptoFunctionService.randomBytes(16);
expect(randomData.byteLength === randomData2.byteLength && randomData !== randomData2).toBeTruthy();
});
it("should not make the same value twice", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const randomData = await cryptoFunctionService.randomBytes(16);
const randomData2 = await cryptoFunctionService.randomBytes(16);
expect(
randomData.byteLength === randomData2.byteLength && randomData !== randomData2
).toBeTruthy();
});
});
});
function testPbkdf2(algorithm: 'sha256' | 'sha512', regularKey: string,
utf8Key: string, unicodeKey: string) {
const regularEmail = 'user@example.com';
const utf8Email = 'üser@example.com';
function testPbkdf2(
algorithm: "sha256" | "sha512",
regularKey: string,
utf8Key: string,
unicodeKey: string
) {
const regularEmail = "user@example.com";
const utf8Email = "üser@example.com";
const regularPassword = 'password';
const utf8Password = 'pǻssword';
const unicodePassword = '😀password🙏';
const regularPassword = "password";
const utf8Password = "pǻssword";
const unicodePassword = "😀password🙏";
it('should create valid ' + algorithm + ' key from regular input', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const key = await cryptoFunctionService.pbkdf2(regularPassword, regularEmail, algorithm, 5000);
expect(Utils.fromBufferToB64(key)).toBe(regularKey);
});
it("should create valid " + algorithm + " key from regular input", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const key = await cryptoFunctionService.pbkdf2(regularPassword, regularEmail, algorithm, 5000);
expect(Utils.fromBufferToB64(key)).toBe(regularKey);
});
it('should create valid ' + algorithm + ' key from utf8 input', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const key = await cryptoFunctionService.pbkdf2(utf8Password, utf8Email, algorithm, 5000);
expect(Utils.fromBufferToB64(key)).toBe(utf8Key);
});
it("should create valid " + algorithm + " key from utf8 input", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const key = await cryptoFunctionService.pbkdf2(utf8Password, utf8Email, algorithm, 5000);
expect(Utils.fromBufferToB64(key)).toBe(utf8Key);
});
it('should create valid ' + algorithm + ' key from unicode input', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const key = await cryptoFunctionService.pbkdf2(unicodePassword, regularEmail, algorithm, 5000);
expect(Utils.fromBufferToB64(key)).toBe(unicodeKey);
});
it("should create valid " + algorithm + " key from unicode input", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const key = await cryptoFunctionService.pbkdf2(unicodePassword, regularEmail, algorithm, 5000);
expect(Utils.fromBufferToB64(key)).toBe(unicodeKey);
});
it('should create valid ' + algorithm + ' key from array buffer input', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const key = await cryptoFunctionService.pbkdf2(Utils.fromUtf8ToArray(regularPassword).buffer,
Utils.fromUtf8ToArray(regularEmail).buffer, algorithm, 5000);
expect(Utils.fromBufferToB64(key)).toBe(regularKey);
});
it("should create valid " + algorithm + " key from array buffer input", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const key = await cryptoFunctionService.pbkdf2(
Utils.fromUtf8ToArray(regularPassword).buffer,
Utils.fromUtf8ToArray(regularEmail).buffer,
algorithm,
5000
);
expect(Utils.fromBufferToB64(key)).toBe(regularKey);
});
}
function testHkdf(algorithm: 'sha256' | 'sha512', regularKey: string, utf8Key: string, unicodeKey: string) {
const ikm = Utils.fromB64ToArray('criAmKtfzxanbgea5/kelQ==');
function testHkdf(
algorithm: "sha256" | "sha512",
regularKey: string,
utf8Key: string,
unicodeKey: string
) {
const ikm = Utils.fromB64ToArray("criAmKtfzxanbgea5/kelQ==");
const regularSalt = 'salt';
const utf8Salt = 'üser_salt';
const unicodeSalt = '😀salt🙏';
const regularSalt = "salt";
const utf8Salt = "üser_salt";
const unicodeSalt = "😀salt🙏";
const regularInfo = 'info';
const utf8Info = 'üser_info';
const unicodeInfo = '😀info🙏';
const regularInfo = "info";
const utf8Info = "üser_info";
const unicodeInfo = "😀info🙏";
it('should create valid ' + algorithm + ' key from regular input', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const key = await cryptoFunctionService.hkdf(ikm, regularSalt, regularInfo, 32, algorithm);
expect(Utils.fromBufferToB64(key)).toBe(regularKey);
});
it("should create valid " + algorithm + " key from regular input", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const key = await cryptoFunctionService.hkdf(ikm, regularSalt, regularInfo, 32, algorithm);
expect(Utils.fromBufferToB64(key)).toBe(regularKey);
});
it('should create valid ' + algorithm + ' key from utf8 input', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const key = await cryptoFunctionService.hkdf(ikm, utf8Salt, utf8Info, 32, algorithm);
expect(Utils.fromBufferToB64(key)).toBe(utf8Key);
});
it("should create valid " + algorithm + " key from utf8 input", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const key = await cryptoFunctionService.hkdf(ikm, utf8Salt, utf8Info, 32, algorithm);
expect(Utils.fromBufferToB64(key)).toBe(utf8Key);
});
it('should create valid ' + algorithm + ' key from unicode input', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const key = await cryptoFunctionService.hkdf(ikm, unicodeSalt, unicodeInfo, 32, algorithm);
expect(Utils.fromBufferToB64(key)).toBe(unicodeKey);
});
it("should create valid " + algorithm + " key from unicode input", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const key = await cryptoFunctionService.hkdf(ikm, unicodeSalt, unicodeInfo, 32, algorithm);
expect(Utils.fromBufferToB64(key)).toBe(unicodeKey);
});
it('should create valid ' + algorithm + ' key from array buffer input', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const key = await cryptoFunctionService.hkdf(ikm, Utils.fromUtf8ToArray(regularSalt).buffer,
Utils.fromUtf8ToArray(regularInfo).buffer, 32, algorithm);
expect(Utils.fromBufferToB64(key)).toBe(regularKey);
});
it("should create valid " + algorithm + " key from array buffer input", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const key = await cryptoFunctionService.hkdf(
ikm,
Utils.fromUtf8ToArray(regularSalt).buffer,
Utils.fromUtf8ToArray(regularInfo).buffer,
32,
algorithm
);
expect(Utils.fromBufferToB64(key)).toBe(regularKey);
});
}
function testHkdfExpand(algorithm: 'sha256' | 'sha512', b64prk: string, outputByteSize: number,
b64ExpectedOkm: string) {
const info = 'info';
function testHkdfExpand(
algorithm: "sha256" | "sha512",
b64prk: string,
outputByteSize: number,
b64ExpectedOkm: string
) {
const info = "info";
it('should create valid ' + algorithm + ' ' + outputByteSize + ' byte okm', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const okm = await cryptoFunctionService.hkdfExpand(Utils.fromB64ToArray(b64prk), info, outputByteSize,
algorithm);
expect(Utils.fromBufferToB64(okm)).toBe(b64ExpectedOkm);
});
it("should create valid " + algorithm + " " + outputByteSize + " byte okm", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const okm = await cryptoFunctionService.hkdfExpand(
Utils.fromB64ToArray(b64prk),
info,
outputByteSize,
algorithm
);
expect(Utils.fromBufferToB64(okm)).toBe(b64ExpectedOkm);
});
}
function testHash(algorithm: 'sha1' | 'sha256' | 'sha512' | 'md5', regularHash: string,
utf8Hash: string, unicodeHash: string) {
const regularValue = 'HashMe!!';
const utf8Value = 'HǻshMe!!';
const unicodeValue = '😀HashMe!!!🙏';
function testHash(
algorithm: "sha1" | "sha256" | "sha512" | "md5",
regularHash: string,
utf8Hash: string,
unicodeHash: string
) {
const regularValue = "HashMe!!";
const utf8Value = "HǻshMe!!";
const unicodeValue = "😀HashMe!!!🙏";
it('should create valid ' + algorithm + ' hash from regular input', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const hash = await cryptoFunctionService.hash(regularValue, algorithm);
expect(Utils.fromBufferToHex(hash)).toBe(regularHash);
});
it("should create valid " + algorithm + " hash from regular input", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const hash = await cryptoFunctionService.hash(regularValue, algorithm);
expect(Utils.fromBufferToHex(hash)).toBe(regularHash);
});
it('should create valid ' + algorithm + ' hash from utf8 input', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const hash = await cryptoFunctionService.hash(utf8Value, algorithm);
expect(Utils.fromBufferToHex(hash)).toBe(utf8Hash);
});
it("should create valid " + algorithm + " hash from utf8 input", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const hash = await cryptoFunctionService.hash(utf8Value, algorithm);
expect(Utils.fromBufferToHex(hash)).toBe(utf8Hash);
});
it('should create valid ' + algorithm + ' hash from unicode input', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const hash = await cryptoFunctionService.hash(unicodeValue, algorithm);
expect(Utils.fromBufferToHex(hash)).toBe(unicodeHash);
});
it("should create valid " + algorithm + " hash from unicode input", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const hash = await cryptoFunctionService.hash(unicodeValue, algorithm);
expect(Utils.fromBufferToHex(hash)).toBe(unicodeHash);
});
it('should create valid ' + algorithm + ' hash from array buffer input', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const hash = await cryptoFunctionService.hash(Utils.fromUtf8ToArray(regularValue).buffer, algorithm);
expect(Utils.fromBufferToHex(hash)).toBe(regularHash);
});
it("should create valid " + algorithm + " hash from array buffer input", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const hash = await cryptoFunctionService.hash(
Utils.fromUtf8ToArray(regularValue).buffer,
algorithm
);
expect(Utils.fromBufferToHex(hash)).toBe(regularHash);
});
}
function testHmac(algorithm: 'sha1' | 'sha256' | 'sha512', mac: string) {
it('should create valid ' + algorithm + ' hmac', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const computedMac = await cryptoFunctionService.hmac(Utils.fromUtf8ToArray('SignMe!!').buffer,
Utils.fromUtf8ToArray('secretkey').buffer, algorithm);
expect(Utils.fromBufferToHex(computedMac)).toBe(mac);
});
function testHmac(algorithm: "sha1" | "sha256" | "sha512", mac: string) {
it("should create valid " + algorithm + " hmac", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const computedMac = await cryptoFunctionService.hmac(
Utils.fromUtf8ToArray("SignMe!!").buffer,
Utils.fromUtf8ToArray("secretkey").buffer,
algorithm
);
expect(Utils.fromBufferToHex(computedMac)).toBe(mac);
});
}
function testHmacFast(algorithm: 'sha1' | 'sha256' | 'sha512', mac: string) {
it('should create valid ' + algorithm + ' hmac', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const keyByteString = Utils.fromBufferToByteString(Utils.fromUtf8ToArray('secretkey').buffer);
const dataByteString = Utils.fromBufferToByteString(Utils.fromUtf8ToArray('SignMe!!').buffer);
const computedMac = await cryptoFunctionService.hmacFast(dataByteString, keyByteString, algorithm);
expect(Utils.fromBufferToHex(Utils.fromByteStringToArray(computedMac).buffer)).toBe(mac);
});
function testHmacFast(algorithm: "sha1" | "sha256" | "sha512", mac: string) {
it("should create valid " + algorithm + " hmac", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const keyByteString = Utils.fromBufferToByteString(Utils.fromUtf8ToArray("secretkey").buffer);
const dataByteString = Utils.fromBufferToByteString(Utils.fromUtf8ToArray("SignMe!!").buffer);
const computedMac = await cryptoFunctionService.hmacFast(
dataByteString,
keyByteString,
algorithm
);
expect(Utils.fromBufferToHex(Utils.fromByteStringToArray(computedMac).buffer)).toBe(mac);
});
}
function testRsaGenerateKeyPair(length: 1024 | 2048 | 4096) {
it('should successfully generate a ' + length + ' bit key pair', async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const keyPair = await cryptoFunctionService.rsaGenerateKeyPair(length);
expect(keyPair[0] == null || keyPair[1] == null).toBe(false);
const publicKey = await cryptoFunctionService.rsaExtractPublicKey(keyPair[1]);
expect(Utils.fromBufferToB64(keyPair[0])).toBe(Utils.fromBufferToB64(publicKey));
}, 30000);
it(
"should successfully generate a " + length + " bit key pair",
async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const keyPair = await cryptoFunctionService.rsaGenerateKeyPair(length);
expect(keyPair[0] == null || keyPair[1] == null).toBe(false);
const publicKey = await cryptoFunctionService.rsaExtractPublicKey(keyPair[1]);
expect(Utils.fromBufferToB64(keyPair[0])).toBe(Utils.fromBufferToB64(publicKey));
},
30000
);
}
function getWebCryptoFunctionService() {
const platformUtilsMock = Substitute.for<PlatformUtilsService>();
platformUtilsMock.isEdge().mimicks(() => navigator.userAgent.indexOf(' Edg/') !== -1);
platformUtilsMock.isIE().mimicks(() => navigator.userAgent.indexOf(' Edg/') === -1 &&
navigator.userAgent.indexOf(' Trident/') !== -1);
const platformUtilsMock = Substitute.for<PlatformUtilsService>();
platformUtilsMock.isEdge().mimicks(() => navigator.userAgent.indexOf(" Edg/") !== -1);
platformUtilsMock
.isIE()
.mimicks(
() =>
navigator.userAgent.indexOf(" Edg/") === -1 &&
navigator.userAgent.indexOf(" Trident/") !== -1
);
return new WebCryptoFunctionService(window, platformUtilsMock);
return new WebCryptoFunctionService(window, platformUtilsMock);
}
function makeStaticByteArray(length: number) {
const arr = new Uint8Array(length);
for (let i = 0; i < length; i++) {
arr[i] = i;
}
return arr;
const arr = new Uint8Array(length);
for (let i = 0; i < length; i++) {
arr[i] = i;
}
return arr;
}

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