Apply Prettier (#581)
This commit is contained in:
parent
8b2dfc6cdc
commit
193434461d
|
@ -0,0 +1 @@
|
||||||
|
* text=auto eol=lf
|
|
@ -1,4 +1,5 @@
|
||||||
## Type of change
|
## Type of change
|
||||||
|
|
||||||
- [ ] Bug fix
|
- [ ] Bug fix
|
||||||
- [ ] New feature development
|
- [ ] New feature development
|
||||||
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
|
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
|
||||||
|
@ -6,22 +7,22 @@
|
||||||
- [ ] Other
|
- [ ] Other
|
||||||
|
|
||||||
## Objective
|
## Objective
|
||||||
|
|
||||||
<!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding-->
|
<!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Code changes
|
## Code changes
|
||||||
|
|
||||||
<!--Explain the changes you've made to each file or major component. This should help the reviewer understand your 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-->
|
<!--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
|
## Testing requirements
|
||||||
|
|
||||||
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
|
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Before you submit
|
## Before you submit
|
||||||
|
|
||||||
- [ ] I have checked for **linting** errors (`npm run lint`) (required)
|
- [ ] 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)
|
- [ ] I have added **unit tests** where it makes sense to do so (encouraged but not required)
|
||||||
- [ ] This change requires a **documentation update** (notify the documentation team)
|
- [ ] This change requires a **documentation update** (notify the documentation team)
|
||||||
|
|
|
@ -20,7 +20,6 @@ jobs:
|
||||||
- name: Print lines of code
|
- name: Print lines of code
|
||||||
run: cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git
|
run: cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git
|
||||||
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build jslib
|
name: Build jslib
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
@ -33,7 +32,7 @@ jobs:
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
||||||
with:
|
with:
|
||||||
node-version: '16'
|
node-version: "16"
|
||||||
|
|
||||||
- name: Install node-gyp
|
- name: Install node-gyp
|
||||||
run: |
|
run: |
|
||||||
|
@ -51,8 +50,8 @@ jobs:
|
||||||
- name: Install Node dependencies
|
- name: Install Node dependencies
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|
||||||
# - name: Run linter
|
- name: Run linter
|
||||||
# run: npm run lint
|
run: npm run lint
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
@ -106,7 +105,7 @@ jobs:
|
||||||
secrets: "devops-alerts-slack-webhook-url"
|
secrets: "devops-alerts-slack-webhook-url"
|
||||||
|
|
||||||
- name: Notify Slack on failure
|
- name: Notify Slack on failure
|
||||||
uses: act10ns/slack@e4e71685b9b239384b0f676a63c32367f59c2522 # v1.2.2
|
uses: act10ns/slack@e4e71685b9b239384b0f676a63c32367f59c2522 # v1.2.2
|
||||||
if: failure()
|
if: failure()
|
||||||
env:
|
env:
|
||||||
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}
|
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
{
|
{
|
||||||
// Use IntelliSense to learn about possible attributes.
|
// Use IntelliSense to learn about possible attributes.
|
||||||
// Hover to view descriptions of existing attributes.
|
// Hover to view descriptions of existing attributes.
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"type": "node",
|
"type": "node",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Jasmine Individual Test",
|
"name": "Jasmine Individual Test",
|
||||||
"program": "${workspaceRoot}\\node_modules\\jasmine\\bin\\jasmine.js",
|
"program": "${workspaceRoot}\\node_modules\\jasmine\\bin\\jasmine.js",
|
||||||
"preLaunchTask": "npm run build",
|
"preLaunchTask": "npm run build",
|
||||||
"args": [
|
"args": ["${workspaceFolder}/dist\\spec\\node\\services\\nodeCryptoFunction.service.spec.js"]
|
||||||
"${workspaceFolder}/dist\\spec\\node\\services\\nodeCryptoFunction.service.spec.js"
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||||
// for the documentation about the tasks.json format
|
// for the documentation about the tasks.json format
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
{
|
||||||
"label": "npm run build",
|
"label": "npm run build",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "npm run build"
|
"command": "npm run build"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,15 +6,11 @@ Please visit our [Community Forums](https://community.bitwarden.com/) for genera
|
||||||
|
|
||||||
Here is how you can get involved:
|
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
|
- **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
|
||||||
* **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)
|
||||||
* **Report a bug or submit a bugfix:** Use Github issues and pull requests
|
- **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
|
||||||
|
|
||||||
* **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
|
## Contributor Agreement
|
||||||
|
|
||||||
|
@ -22,9 +18,9 @@ Please sign the [Contributor Agreement](https://cla-assistant.io/bitwarden/jslib
|
||||||
|
|
||||||
## Pull Request Guidelines
|
## Pull Request Guidelines
|
||||||
|
|
||||||
* use `npm run lint` and fix any linting suggestions before submitting a pull request
|
- use `npm run lint` and fix any linting suggestions before submitting a pull request
|
||||||
* commit any pull requests against the `master` branch
|
- commit any pull requests against the `master` branch
|
||||||
* include a link to your Community Forums post
|
- include a link to your Community Forums post
|
||||||
|
|
||||||
# Introduction to jslib and git submodules
|
# 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)).
|
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
|
# 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.
|
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
|
## Prerequisites
|
||||||
|
|
||||||
1. git bash or other git command line
|
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:
|
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)
|
- `./<your project(s) directory>`; we'll call this `/dev` ('cause why not)
|
||||||
* jslib - `git clone https://github.com/bitwarden/jslib.git` (/dev/jslib)
|
- jslib - `git clone https://github.com/bitwarden/jslib.git` (/dev/jslib)
|
||||||
* web - `git clone --recurse-submodules https://github.com/bitwarden/web.git` (/dev/web)
|
- 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)
|
- 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)
|
- 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)
|
- 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
|
## 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`.
|
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 each project other than jslib, run the following:
|
||||||
|
|
||||||
* For macOS/Linux: `npm run symlink:mac`
|
- For macOS/Linux: `npm run symlink:mac`
|
||||||
* For Windows: `npm run symlink:win`
|
- 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).
|
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
|
## 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.
|
- 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.
|
- 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.
|
- 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
|
### 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`.
|
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:
|
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 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 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).
|
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`
|
4. Add your changes: `git add jslib`
|
||||||
5. Commit your changes: `git commit -m "update jslib version"`
|
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.
|
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
|
## 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:
|
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:
|
||||||
|
|
||||||
|
|
15
README.md
15
README.md
|
@ -5,13 +5,14 @@
|
||||||
Common code referenced across Bitwarden JavaScript projects.
|
Common code referenced across Bitwarden JavaScript projects.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
* [Node.js](https://nodejs.org) v16.13.1 or greater
|
|
||||||
* NPM v8
|
- [Node.js](https://nodejs.org) v16.13.1 or greater
|
||||||
* Git
|
- NPM v8
|
||||||
* node-gyp
|
- Git
|
||||||
|
- node-gyp
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
* *Microsoft Build Tools 2015* in 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/)
|
- [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.
|
either by downloading it seperately or through the Visual Studio Installer.
|
||||||
|
|
|
@ -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
|
- 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.
|
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
|
- 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
|
- 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
|
degradation of our service. Only interact with accounts you own or with explicit permission of the
|
||||||
account holder.
|
account holder.
|
||||||
|
|
|
@ -1,124 +1,120 @@
|
||||||
import {
|
import { Directive, Input, OnChanges, SimpleChanges } from "@angular/core";
|
||||||
Directive,
|
|
||||||
Input,
|
|
||||||
OnChanges,
|
|
||||||
SimpleChanges,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import {
|
import { CdkDragDrop, moveItemInArray } from "@angular/cdk/drag-drop";
|
||||||
CdkDragDrop,
|
|
||||||
moveItemInArray,
|
|
||||||
} from '@angular/cdk/drag-drop';
|
|
||||||
|
|
||||||
import { EventService } from 'jslib-common/abstractions/event.service';
|
import { EventService } from "jslib-common/abstractions/event.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
|
|
||||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||||
import { FieldView } from 'jslib-common/models/view/fieldView';
|
import { FieldView } from "jslib-common/models/view/fieldView";
|
||||||
|
|
||||||
import { CipherType } from 'jslib-common/enums/cipherType';
|
import { CipherType } from "jslib-common/enums/cipherType";
|
||||||
import { EventType } from 'jslib-common/enums/eventType';
|
import { EventType } from "jslib-common/enums/eventType";
|
||||||
import { FieldType } from 'jslib-common/enums/fieldType';
|
import { FieldType } from "jslib-common/enums/fieldType";
|
||||||
|
|
||||||
import { Utils } from 'jslib-common/misc/utils';
|
import { Utils } from "jslib-common/misc/utils";
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class AddEditCustomFieldsComponent implements OnChanges {
|
export class AddEditCustomFieldsComponent implements OnChanges {
|
||||||
@Input() cipher: CipherView;
|
@Input() cipher: CipherView;
|
||||||
@Input() thisCipherType: CipherType;
|
@Input() thisCipherType: CipherType;
|
||||||
@Input() editMode: boolean;
|
@Input() editMode: boolean;
|
||||||
|
|
||||||
addFieldType: FieldType = FieldType.Text;
|
addFieldType: FieldType = FieldType.Text;
|
||||||
addFieldTypeOptions: any[];
|
addFieldTypeOptions: any[];
|
||||||
addFieldLinkedTypeOption: any;
|
addFieldLinkedTypeOption: any;
|
||||||
linkedFieldOptions: any[] = [];
|
linkedFieldOptions: any[] = [];
|
||||||
|
|
||||||
cipherType = CipherType;
|
cipherType = CipherType;
|
||||||
fieldType = FieldType;
|
fieldType = FieldType;
|
||||||
eventType = EventType;
|
eventType = EventType;
|
||||||
|
|
||||||
constructor(private i18nService: I18nService, private eventService: EventService) {
|
constructor(private i18nService: I18nService, private eventService: EventService) {
|
||||||
this.addFieldTypeOptions = [
|
this.addFieldTypeOptions = [
|
||||||
{ name: i18nService.t('cfTypeText'), value: FieldType.Text },
|
{ name: i18nService.t("cfTypeText"), value: FieldType.Text },
|
||||||
{ name: i18nService.t('cfTypeHidden'), value: FieldType.Hidden },
|
{ name: i18nService.t("cfTypeHidden"), value: FieldType.Hidden },
|
||||||
{ name: i18nService.t('cfTypeBoolean'), value: FieldType.Boolean },
|
{ name: i18nService.t("cfTypeBoolean"), value: FieldType.Boolean },
|
||||||
];
|
];
|
||||||
this.addFieldLinkedTypeOption = { name: this.i18nService.t('cfTypeLinked'), value: FieldType.Linked };
|
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) {
|
const f = new FieldView();
|
||||||
if (changes.thisCipherType != null) {
|
f.type = this.addFieldType;
|
||||||
this.setLinkedFieldOptions();
|
f.newField = true;
|
||||||
|
|
||||||
if (!changes.thisCipherType.firstChange) {
|
if (f.type === FieldType.Linked) {
|
||||||
this.resetCipherLinkedFields();
|
f.linkedId = this.linkedFieldOptions[0].value;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addField() {
|
this.cipher.fields.push(f);
|
||||||
if (this.cipher.fields == null) {
|
}
|
||||||
this.cipher.fields = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const f = new FieldView();
|
removeField(field: FieldView) {
|
||||||
f.type = this.addFieldType;
|
const i = this.cipher.fields.indexOf(field);
|
||||||
f.newField = true;
|
if (i > -1) {
|
||||||
|
this.cipher.fields.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (f.type === FieldType.Linked) {
|
toggleFieldValue(field: FieldView) {
|
||||||
f.linkedId = this.linkedFieldOptions[0].value;
|
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 options: any = [];
|
||||||
const i = this.cipher.fields.indexOf(field);
|
this.cipher.linkedFieldOptions.forEach((linkedFieldOption, id) =>
|
||||||
if (i > -1) {
|
options.push({ name: this.i18nService.t(linkedFieldOption.i18nKey), value: id })
|
||||||
this.cipher.fields.splice(i, 1);
|
);
|
||||||
}
|
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) {
|
// Delete any Linked custom fields if the item type does not support them
|
||||||
const f = (field as any);
|
if (this.cipher.linkedFieldOptions == null) {
|
||||||
f.showValue = !f.showValue;
|
this.cipher.fields = this.cipher.fields.filter((f) => f.type !== FieldType.Linked);
|
||||||
if (this.editMode && f.showValue) {
|
return;
|
||||||
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipher.id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trackByFunction(index: number, item: any) {
|
this.cipher.fields
|
||||||
return index;
|
.filter((f) => f.type === FieldType.Linked)
|
||||||
}
|
.forEach((f) => (f.linkedId = this.linkedFieldOptions[0].value));
|
||||||
|
}
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,250 +1,291 @@
|
||||||
import {
|
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||||
Directive,
|
|
||||||
EventEmitter,
|
|
||||||
Input,
|
|
||||||
OnInit,
|
|
||||||
Output,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
|
|
||||||
import { Cipher } from 'jslib-common/models/domain/cipher';
|
import { Cipher } from "jslib-common/models/domain/cipher";
|
||||||
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 { AttachmentView } from "jslib-common/models/view/attachmentView";
|
||||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class AttachmentsComponent implements OnInit {
|
export class AttachmentsComponent implements OnInit {
|
||||||
@Input() cipherId: string;
|
@Input() cipherId: string;
|
||||||
@Output() onUploadedAttachment = new EventEmitter();
|
@Output() onUploadedAttachment = new EventEmitter();
|
||||||
@Output() onDeletedAttachment = new EventEmitter();
|
@Output() onDeletedAttachment = new EventEmitter();
|
||||||
@Output() onReuploadedAttachment = new EventEmitter();
|
@Output() onReuploadedAttachment = new EventEmitter();
|
||||||
|
|
||||||
cipher: CipherView;
|
cipher: CipherView;
|
||||||
cipherDomain: Cipher;
|
cipherDomain: Cipher;
|
||||||
hasUpdatedKey: boolean;
|
hasUpdatedKey: boolean;
|
||||||
canAccessAttachments: boolean;
|
canAccessAttachments: boolean;
|
||||||
formPromise: Promise<any>;
|
formPromise: Promise<any>;
|
||||||
deletePromises: { [id: string]: Promise<any>; } = {};
|
deletePromises: { [id: string]: Promise<any> } = {};
|
||||||
reuploadPromises: { [id: string]: Promise<any>; } = {};
|
reuploadPromises: { [id: string]: Promise<any> } = {};
|
||||||
emergencyAccessId?: string = null;
|
emergencyAccessId?: string = null;
|
||||||
|
|
||||||
constructor(protected cipherService: CipherService, protected i18nService: I18nService,
|
constructor(
|
||||||
protected cryptoService: CryptoService, protected platformUtilsService: PlatformUtilsService,
|
protected cipherService: CipherService,
|
||||||
protected apiService: ApiService, protected win: Window,
|
protected i18nService: I18nService,
|
||||||
protected logService: LogService, protected stateService: StateService) { }
|
protected cryptoService: CryptoService,
|
||||||
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
|
protected apiService: ApiService,
|
||||||
|
protected win: Window,
|
||||||
|
protected logService: LogService,
|
||||||
|
protected stateService: StateService
|
||||||
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
await this.init();
|
await this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
async submit() {
|
||||||
|
if (!this.hasUpdatedKey) {
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"error",
|
||||||
|
this.i18nService.t("errorOccurred"),
|
||||||
|
this.i18nService.t("updateKey")
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
const fileEl = document.getElementById("file") as HTMLInputElement;
|
||||||
if (!this.hasUpdatedKey) {
|
const files = fileEl.files;
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
if (files == null || files.length === 0) {
|
||||||
this.i18nService.t('updateKey'));
|
this.platformUtilsService.showToast(
|
||||||
return;
|
"error",
|
||||||
}
|
this.i18nService.t("errorOccurred"),
|
||||||
|
this.i18nService.t("selectFile")
|
||||||
const fileEl = document.getElementById('file') as HTMLInputElement;
|
);
|
||||||
const files = fileEl.files;
|
return;
|
||||||
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 = '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(attachment: AttachmentView) {
|
if (files[0].size > 524288000) {
|
||||||
if (this.deletePromises[attachment.id] != null) {
|
// 500 MB
|
||||||
return;
|
this.platformUtilsService.showToast(
|
||||||
}
|
"error",
|
||||||
|
this.i18nService.t("errorOccurred"),
|
||||||
const confirmed = await this.platformUtilsService.showDialog(
|
this.i18nService.t("maxFileSize")
|
||||||
this.i18nService.t('deleteAttachmentConfirmation'), this.i18nService.t('deleteAttachment'),
|
);
|
||||||
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
|
return;
|
||||||
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) {
|
try {
|
||||||
const a = (attachment as any);
|
this.formPromise = this.saveCipherAttachment(files[0]);
|
||||||
if (a.downloading) {
|
this.cipherDomain = await this.formPromise;
|
||||||
return;
|
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) {
|
// reset file input
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('premiumRequired'),
|
// ref: https://stackoverflow.com/a/20552042
|
||||||
this.i18nService.t('premiumRequiredDesc'));
|
fileEl.type = "";
|
||||||
return;
|
fileEl.type = "file";
|
||||||
}
|
fileEl.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
let url: string;
|
async delete(attachment: AttachmentView) {
|
||||||
try {
|
if (this.deletePromises[attachment.id] != null) {
|
||||||
const attachmentDownloadResponse = await this.apiService.getAttachmentData(this.cipher.id, attachment.id,
|
return;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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;
|
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) {
|
if (response.status !== 200) {
|
||||||
this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred'));
|
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
|
||||||
a.downloading = false;
|
a.downloading = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const buf = await response.arrayBuffer();
|
// 2. Resave
|
||||||
const key = attachment.key != null ? attachment.key :
|
const buf = await response.arrayBuffer();
|
||||||
await this.cryptoService.getOrgKey(this.cipher.organizationId);
|
const key =
|
||||||
const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
|
attachment.key != null
|
||||||
this.platformUtilsService.saveFile(this.win, decBuf, null, attachment.fileName);
|
? 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) {
|
} catch (e) {
|
||||||
this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred'));
|
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
|
||||||
}
|
}
|
||||||
|
|
||||||
a.downloading = false;
|
a.downloading = false;
|
||||||
|
});
|
||||||
|
await this.reuploadPromises[attachment.id];
|
||||||
|
} catch (e) {
|
||||||
|
this.logService.error(e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected async init() {
|
protected loadCipher() {
|
||||||
this.cipherDomain = await this.loadCipher();
|
return this.cipherService.get(this.cipherId);
|
||||||
this.cipher = await this.cipherDomain.decrypt();
|
}
|
||||||
|
|
||||||
this.hasUpdatedKey = await this.cryptoService.hasEncKey();
|
protected saveCipherAttachment(file: File) {
|
||||||
const canAccessPremium = await this.stateService.getCanAccessPremium();
|
return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file);
|
||||||
this.canAccessAttachments = canAccessPremium || this.cipher.organizationId != null;
|
}
|
||||||
|
|
||||||
if (!this.canAccessAttachments) {
|
protected deleteCipherAttachment(attachmentId: string) {
|
||||||
const confirmed = await this.platformUtilsService.showDialog(
|
return this.cipherService.deleteAttachmentWithServer(this.cipher.id, attachmentId);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,138 +1,143 @@
|
||||||
import {
|
import { Component, Input, OnChanges, OnInit } from "@angular/core";
|
||||||
Component,
|
import { DomSanitizer } from "@angular/platform-browser";
|
||||||
Input,
|
|
||||||
OnChanges,
|
|
||||||
OnInit,
|
|
||||||
} from '@angular/core';
|
|
||||||
import { DomSanitizer } from '@angular/platform-browser';
|
|
||||||
|
|
||||||
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
|
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
|
|
||||||
import { Utils } from 'jslib-common/misc/utils';
|
import { Utils } from "jslib-common/misc/utils";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-avatar',
|
selector: "app-avatar",
|
||||||
template: '<img [src]="sanitizer.bypassSecurityTrustResourceUrl(src)" title="{{data}}" ' +
|
template:
|
||||||
'[ngClass]="{\'rounded-circle\': circle}">',
|
'<img [src]="sanitizer.bypassSecurityTrustResourceUrl(src)" title="{{data}}" ' +
|
||||||
|
"[ngClass]=\"{'rounded-circle': circle}\">",
|
||||||
})
|
})
|
||||||
export class AvatarComponent implements OnChanges, OnInit {
|
export class AvatarComponent implements OnChanges, OnInit {
|
||||||
@Input() data: string;
|
@Input() data: string;
|
||||||
@Input() email: string;
|
@Input() email: string;
|
||||||
@Input() size = 45;
|
@Input() size = 45;
|
||||||
@Input() charCount = 2;
|
@Input() charCount = 2;
|
||||||
@Input() textColor = '#ffffff';
|
@Input() textColor = "#ffffff";
|
||||||
@Input() fontSize = 20;
|
@Input() fontSize = 20;
|
||||||
@Input() fontWeight = 300;
|
@Input() fontWeight = 300;
|
||||||
@Input() dynamic = false;
|
@Input() dynamic = false;
|
||||||
@Input() circle = false;
|
@Input() circle = false;
|
||||||
|
|
||||||
src: string;
|
src: string;
|
||||||
|
|
||||||
constructor(public sanitizer: DomSanitizer, private cryptoFunctionService: CryptoFunctionService,
|
constructor(
|
||||||
private stateService: StateService) { }
|
public sanitizer: DomSanitizer,
|
||||||
|
private cryptoFunctionService: CryptoFunctionService,
|
||||||
|
private stateService: StateService
|
||||||
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
if (!this.dynamic) {
|
if (!this.dynamic) {
|
||||||
this.generate();
|
this.generate();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ngOnChanges() {
|
ngOnChanges() {
|
||||||
if (this.dynamic) {
|
if (this.dynamic) {
|
||||||
this.generate();
|
this.generate();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async generate() {
|
private async generate() {
|
||||||
const enableGravatars = await this.stateService.getEnableGravitars();
|
const enableGravatars = await this.stateService.getEnableGravitars();
|
||||||
if (enableGravatars && this.email != null) {
|
if (enableGravatars && this.email != null) {
|
||||||
const hashBytes = await this.cryptoFunctionService.hash(this.email.toLowerCase().trim(), 'md5');
|
const hashBytes = await this.cryptoFunctionService.hash(
|
||||||
const hash = Utils.fromBufferToHex(hashBytes).toLowerCase();
|
this.email.toLowerCase().trim(),
|
||||||
this.src = 'https://www.gravatar.com/avatar/' + hash + '?s=' + this.size + '&r=pg&d=retro';
|
"md5"
|
||||||
} else {
|
);
|
||||||
let chars: string = null;
|
const hash = Utils.fromBufferToHex(hashBytes).toLowerCase();
|
||||||
const upperData = this.data.toUpperCase();
|
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) {
|
if (this.charCount > 1) {
|
||||||
chars = this.getFirstLetters(upperData, this.charCount);
|
chars = this.getFirstLetters(upperData, this.charCount);
|
||||||
}
|
}
|
||||||
if (chars == null) {
|
if (chars == null) {
|
||||||
chars = this.unicodeSafeSubstring(upperData, this.charCount);
|
chars = this.unicodeSafeSubstring(upperData, this.charCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the chars contain an emoji, only show it.
|
// If the chars contain an emoji, only show it.
|
||||||
if (chars.match(Utils.regexpEmojiPresentation)) {
|
if (chars.match(Utils.regexpEmojiPresentation)) {
|
||||||
chars = chars.match(Utils.regexpEmojiPresentation)[0];
|
chars = chars.match(Utils.regexpEmojiPresentation)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
const charObj = this.getCharText(chars);
|
const charObj = this.getCharText(chars);
|
||||||
const color = this.stringToColor(upperData);
|
const color = this.stringToColor(upperData);
|
||||||
const svg = this.getSvg(this.size, color);
|
const svg = this.getSvg(this.size, color);
|
||||||
svg.appendChild(charObj);
|
svg.appendChild(charObj);
|
||||||
const html = window.document.createElement('div').appendChild(svg).outerHTML;
|
const html = window.document.createElement("div").appendChild(svg).outerHTML;
|
||||||
const svgHtml = window.btoa(unescape(encodeURIComponent(html)));
|
const svgHtml = window.btoa(unescape(encodeURIComponent(html)));
|
||||||
this.src = 'data:image/svg+xml;base64,' + svgHtml;
|
this.src = "data:image/svg+xml;base64," + svgHtml;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private stringToColor(str: string): string {
|
private stringToColor(str: string): string {
|
||||||
let hash = 0;
|
let hash = 0;
|
||||||
for (let i = 0; i < str.length; i++) {
|
for (let i = 0; i < str.length; i++) {
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
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;
|
|
||||||
}
|
}
|
||||||
|
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 {
|
private getFirstLetters(data: string, count: number): string {
|
||||||
const parts = data.split(' ');
|
const parts = data.split(" ");
|
||||||
if (parts.length > 1) {
|
if (parts.length > 1) {
|
||||||
let text = '';
|
let text = "";
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
text += this.unicodeSafeSubstring(parts[i], 1);
|
text += this.unicodeSafeSubstring(parts[i], 1);
|
||||||
}
|
}
|
||||||
return text;
|
return text;
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private getSvg(size: number, color: string): HTMLElement {
|
private getSvg(size: number, color: string): HTMLElement {
|
||||||
const svgTag = window.document.createElement('svg');
|
const svgTag = window.document.createElement("svg");
|
||||||
svgTag.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
svgTag.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
||||||
svgTag.setAttribute('pointer-events', 'none');
|
svgTag.setAttribute("pointer-events", "none");
|
||||||
svgTag.setAttribute('width', size.toString());
|
svgTag.setAttribute("width", size.toString());
|
||||||
svgTag.setAttribute('height', size.toString());
|
svgTag.setAttribute("height", size.toString());
|
||||||
svgTag.style.backgroundColor = color;
|
svgTag.style.backgroundColor = color;
|
||||||
svgTag.style.width = size + 'px';
|
svgTag.style.width = size + "px";
|
||||||
svgTag.style.height = size + 'px';
|
svgTag.style.height = size + "px";
|
||||||
return svgTag;
|
return svgTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCharText(character: string): HTMLElement {
|
private getCharText(character: string): HTMLElement {
|
||||||
const textTag = window.document.createElement('text');
|
const textTag = window.document.createElement("text");
|
||||||
textTag.setAttribute('text-anchor', 'middle');
|
textTag.setAttribute("text-anchor", "middle");
|
||||||
textTag.setAttribute('y', '50%');
|
textTag.setAttribute("y", "50%");
|
||||||
textTag.setAttribute('x', '50%');
|
textTag.setAttribute("x", "50%");
|
||||||
textTag.setAttribute('dy', '0.35em');
|
textTag.setAttribute("dy", "0.35em");
|
||||||
textTag.setAttribute('pointer-events', 'auto');
|
textTag.setAttribute("pointer-events", "auto");
|
||||||
textTag.setAttribute('fill', this.textColor);
|
textTag.setAttribute("fill", this.textColor);
|
||||||
textTag.setAttribute('font-family', '"Open Sans","Helvetica Neue",Helvetica,Arial,' +
|
textTag.setAttribute(
|
||||||
'sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"');
|
"font-family",
|
||||||
textTag.textContent = character;
|
'"Open Sans","Helvetica Neue",Helvetica,Arial,' +
|
||||||
textTag.style.fontWeight = this.fontWeight.toString();
|
'sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"'
|
||||||
textTag.style.fontSize = this.fontSize + 'px';
|
);
|
||||||
return textTag;
|
textTag.textContent = character;
|
||||||
}
|
textTag.style.fontWeight = this.fontWeight.toString();
|
||||||
|
textTag.style.fontSize = this.fontSize + "px";
|
||||||
|
return textTag;
|
||||||
|
}
|
||||||
|
|
||||||
private unicodeSafeSubstring(str: string, count: number) {
|
private unicodeSafeSubstring(str: string, count: number) {
|
||||||
const characters = str.match(/./ug);
|
const characters = str.match(/./gu);
|
||||||
return characters != null ? characters.slice(0, count).join('') : '';
|
return characters != null ? characters.slice(0, count).join("") : "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,35 @@
|
||||||
<div #callout class="callout callout-{{calloutStyle}}" [ngClass]="{'clickable': clickable}"
|
<div
|
||||||
[attr.role]="useAlertRole ? 'alert' : null">
|
#callout
|
||||||
<h3 class="callout-heading" *ngIf="title">
|
class="callout callout-{{ calloutStyle }}"
|
||||||
<i class="fa {{icon}}" *ngIf="icon" aria-hidden="true"></i>
|
[ngClass]="{ clickable: clickable }"
|
||||||
{{title}}
|
[attr.role]="useAlertRole ? 'alert' : null"
|
||||||
</h3>
|
>
|
||||||
<div class="enforced-policy-options" *ngIf="enforcedPolicyOptions">
|
<h3 class="callout-heading" *ngIf="title">
|
||||||
{{enforcedPolicyMessage}}
|
<i class="fa {{ icon }}" *ngIf="icon" aria-hidden="true"></i>
|
||||||
<ul>
|
{{ title }}
|
||||||
<li *ngIf="enforcedPolicyOptions?.minComplexity > 0">
|
</h3>
|
||||||
{{'policyInEffectMinComplexity' | i18n : getPasswordScoreAlertDisplay()}}
|
<div class="enforced-policy-options" *ngIf="enforcedPolicyOptions">
|
||||||
</li>
|
{{ enforcedPolicyMessage }}
|
||||||
<li *ngIf="enforcedPolicyOptions?.minLength > 0">
|
<ul>
|
||||||
{{'policyInEffectMinLength' | i18n : enforcedPolicyOptions?.minLength.toString()}}
|
<li *ngIf="enforcedPolicyOptions?.minComplexity > 0">
|
||||||
</li>
|
{{ "policyInEffectMinComplexity" | i18n: getPasswordScoreAlertDisplay() }}
|
||||||
<li *ngIf="enforcedPolicyOptions?.requireUpper">
|
</li>
|
||||||
{{'policyInEffectUppercase' | i18n}}</li>
|
<li *ngIf="enforcedPolicyOptions?.minLength > 0">
|
||||||
<li *ngIf="enforcedPolicyOptions?.requireLower">
|
{{ "policyInEffectMinLength" | i18n: enforcedPolicyOptions?.minLength.toString() }}
|
||||||
{{'policyInEffectLowercase' | i18n}}</li>
|
</li>
|
||||||
<li *ngIf="enforcedPolicyOptions?.requireNumbers">
|
<li *ngIf="enforcedPolicyOptions?.requireUpper">
|
||||||
{{'policyInEffectNumbers' | i18n}}</li>
|
{{ "policyInEffectUppercase" | i18n }}
|
||||||
<li *ngIf="enforcedPolicyOptions?.requireSpecial">
|
</li>
|
||||||
{{'policyInEffectSpecial' | i18n : '!@#$%^&*'}}</li>
|
<li *ngIf="enforcedPolicyOptions?.requireLower">
|
||||||
</ul>
|
{{ "policyInEffectLowercase" | i18n }}
|
||||||
</div>
|
</li>
|
||||||
<ng-content></ng-content>
|
<li *ngIf="enforcedPolicyOptions?.requireNumbers">
|
||||||
|
{{ "policyInEffectNumbers" | i18n }}
|
||||||
|
</li>
|
||||||
|
<li *ngIf="enforcedPolicyOptions?.requireSpecial">
|
||||||
|
{{ "policyInEffectSpecial" | i18n: "!@#$%^&*" }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<ng-content></ng-content>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,83 +1,79 @@
|
||||||
import {
|
import { Component, Input, OnInit } from "@angular/core";
|
||||||
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({
|
@Component({
|
||||||
selector: 'app-callout',
|
selector: "app-callout",
|
||||||
templateUrl: 'callout.component.html',
|
templateUrl: "callout.component.html",
|
||||||
})
|
})
|
||||||
export class CalloutComponent implements OnInit {
|
export class CalloutComponent implements OnInit {
|
||||||
@Input() type = 'info';
|
@Input() type = "info";
|
||||||
@Input() icon: string;
|
@Input() icon: string;
|
||||||
@Input() title: string;
|
@Input() title: string;
|
||||||
@Input() clickable: boolean;
|
@Input() clickable: boolean;
|
||||||
@Input() enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
@Input() enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
||||||
@Input() enforcedPolicyMessage: string;
|
@Input() enforcedPolicyMessage: string;
|
||||||
@Input() useAlertRole = false;
|
@Input() useAlertRole = false;
|
||||||
|
|
||||||
calloutStyle: string;
|
calloutStyle: string;
|
||||||
|
|
||||||
constructor(private i18nService: I18nService) { }
|
constructor(private i18nService: I18nService) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.calloutStyle = this.type;
|
this.calloutStyle = this.type;
|
||||||
|
|
||||||
if (this.enforcedPolicyMessage === undefined) {
|
if (this.enforcedPolicyMessage === undefined) {
|
||||||
this.enforcedPolicyMessage = this.i18nService.t('masterPasswordPolicyInEffect');
|
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';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getPasswordScoreAlertDisplay() {
|
if (this.type === "warning" || this.type === "danger") {
|
||||||
if (this.enforcedPolicyOptions == null) {
|
if (this.type === "danger") {
|
||||||
return '';
|
this.calloutStyle = "danger";
|
||||||
}
|
}
|
||||||
|
if (this.title === undefined) {
|
||||||
let str: string;
|
this.title = this.i18nService.t("warning");
|
||||||
switch (this.enforcedPolicyOptions.minComplexity) {
|
}
|
||||||
case 4:
|
if (this.icon === undefined) {
|
||||||
str = this.i18nService.t('strong');
|
this.icon = "fa-warning";
|
||||||
break;
|
}
|
||||||
case 3:
|
} else if (this.type === "error") {
|
||||||
str = this.i18nService.t('good');
|
this.calloutStyle = "danger";
|
||||||
break;
|
if (this.title === undefined) {
|
||||||
default:
|
this.title = this.i18nService.t("error");
|
||||||
str = this.i18nService.t('weak');
|
}
|
||||||
break;
|
if (this.icon === undefined) {
|
||||||
}
|
this.icon = "fa-bolt";
|
||||||
return str + ' (' + this.enforcedPolicyOptions.minComplexity + ')';
|
}
|
||||||
|
} 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 + ")";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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()
|
@Directive()
|
||||||
export abstract class CaptchaProtectedComponent {
|
export abstract class CaptchaProtectedComponent {
|
||||||
@Input() captchaSiteKey: string = null;
|
@Input() captchaSiteKey: string = null;
|
||||||
captchaToken: string = null;
|
captchaToken: string = null;
|
||||||
captcha: CaptchaIFrame;
|
captcha: CaptchaIFrame;
|
||||||
|
|
||||||
constructor(protected environmentService: EnvironmentService, protected i18nService: I18nService,
|
constructor(
|
||||||
protected platformUtilsService: PlatformUtilsService) { }
|
protected environmentService: EnvironmentService,
|
||||||
|
protected i18nService: I18nService,
|
||||||
|
protected platformUtilsService: PlatformUtilsService
|
||||||
|
) {}
|
||||||
|
|
||||||
async setupCaptcha() {
|
async setupCaptcha() {
|
||||||
const webVaultUrl = this.environmentService.getWebVaultUrl();
|
const webVaultUrl = this.environmentService.getWebVaultUrl();
|
||||||
|
|
||||||
this.captcha = new CaptchaIFrame(window, webVaultUrl,
|
this.captcha = new CaptchaIFrame(
|
||||||
this.i18nService, (token: string) => {
|
window,
|
||||||
this.captchaToken = token;
|
webVaultUrl,
|
||||||
}, (error: string) => {
|
this.i18nService,
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), error);
|
(token: string) => {
|
||||||
}, (info: string) => {
|
this.captchaToken = token;
|
||||||
this.platformUtilsService.showToast('info', this.i18nService.t('info'), info);
|
},
|
||||||
}
|
(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() {
|
this.captchaSiteKey = response.captchaSiteKey;
|
||||||
return !Utils.isNullOrWhitespace(this.captchaSiteKey);
|
this.captcha.init(response.captchaSiteKey);
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
protected handleCaptchaRequired(response: { captchaSiteKey: string; }): boolean {
|
|
||||||
if (Utils.isNullOrWhitespace(response.captchaSiteKey)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.captchaSiteKey = response.captchaSiteKey;
|
|
||||||
this.captcha.init(response.captchaSiteKey);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
|
|
||||||
import { EncString } from 'jslib-common/models/domain/encString';
|
import { EncString } from "jslib-common/models/domain/encString";
|
||||||
import { MasterPasswordPolicyOptions } from 'jslib-common/models/domain/masterPasswordPolicyOptions';
|
import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPasswordPolicyOptions";
|
||||||
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
|
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
|
||||||
|
|
||||||
import { KdfType } from 'jslib-common/enums/kdfType';
|
import { KdfType } from "jslib-common/enums/kdfType";
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class ChangePasswordComponent implements OnInit {
|
export class ChangePasswordComponent implements OnInit {
|
||||||
masterPassword: string;
|
masterPassword: string;
|
||||||
masterPasswordRetype: string;
|
masterPasswordRetype: string;
|
||||||
formPromise: Promise<any>;
|
formPromise: Promise<any>;
|
||||||
masterPasswordScore: number;
|
masterPasswordScore: number;
|
||||||
enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
||||||
|
|
||||||
protected email: string;
|
protected email: string;
|
||||||
protected kdf: KdfType;
|
protected kdf: KdfType;
|
||||||
protected kdfIterations: number;
|
protected kdfIterations: number;
|
||||||
|
|
||||||
private masterPasswordStrengthTimeout: any;
|
private masterPasswordStrengthTimeout: any;
|
||||||
|
|
||||||
constructor(protected i18nService: I18nService, protected cryptoService: CryptoService,
|
constructor(
|
||||||
protected messagingService: MessagingService, protected passwordGenerationService: PasswordGenerationService,
|
protected i18nService: I18nService,
|
||||||
protected platformUtilsService: PlatformUtilsService, protected policyService: PolicyService,
|
protected cryptoService: CryptoService,
|
||||||
protected stateService: StateService) { }
|
protected messagingService: MessagingService,
|
||||||
|
protected passwordGenerationService: PasswordGenerationService,
|
||||||
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
|
protected policyService: PolicyService,
|
||||||
|
protected stateService: StateService
|
||||||
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.email = await this.stateService.getEmail();
|
this.email = await this.stateService.getEmail();
|
||||||
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
|
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
async submit() {
|
||||||
|
if (!(await this.strongPassword())) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
if (!(await this.setupSubmitActions())) {
|
||||||
if (!await this.strongPassword()) {
|
return;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async setupSubmitActions(): Promise<boolean> {
|
const email = await this.stateService.getEmail();
|
||||||
// Override in sub-class
|
if (this.kdf == null) {
|
||||||
// Can be used for additional validation and/or other processes the should occur before changing passwords
|
this.kdf = await this.stateService.getKdfType();
|
||||||
return true;
|
}
|
||||||
|
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,
|
await this.performSubmitActions(masterPasswordHash, key, encKey);
|
||||||
encKey: [SymmetricCryptoKey, EncString]) {
|
}
|
||||||
// Override in sub-class
|
|
||||||
|
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> {
|
const strengthResult = this.passwordGenerationService.passwordStrength(
|
||||||
if (this.masterPassword == null || this.masterPassword === '') {
|
this.masterPassword,
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
this.getPasswordStrengthUserInput()
|
||||||
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,
|
if (
|
||||||
this.getPasswordStrengthUserInput());
|
this.enforcedPolicyOptions != null &&
|
||||||
|
!this.policyService.evaluateMasterPassword(
|
||||||
if (this.enforcedPolicyOptions != null &&
|
strengthResult.score,
|
||||||
!this.policyService.evaluateMasterPassword(
|
this.masterPassword,
|
||||||
strengthResult.score,
|
this.enforcedPolicyOptions
|
||||||
this.masterPassword,
|
)
|
||||||
this.enforcedPolicyOptions)) {
|
) {
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
this.platformUtilsService.showToast(
|
||||||
this.i18nService.t('masterPasswordPolicyRequirementsNotMet'));
|
"error",
|
||||||
return false;
|
this.i18nService.t("errorOccurred"),
|
||||||
}
|
this.i18nService.t("masterPasswordPolicyRequirementsNotMet")
|
||||||
|
);
|
||||||
if (strengthResult != null && strengthResult.score < 3) {
|
return false;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePasswordStrength() {
|
if (strengthResult != null && strengthResult.score < 3) {
|
||||||
if (this.masterPasswordStrengthTimeout != null) {
|
const result = await this.platformUtilsService.showDialog(
|
||||||
clearTimeout(this.masterPasswordStrengthTimeout);
|
this.i18nService.t("weakMasterPasswordDesc"),
|
||||||
}
|
this.i18nService.t("weakMasterPassword"),
|
||||||
this.masterPasswordStrengthTimeout = setTimeout(() => {
|
this.i18nService.t("yes"),
|
||||||
const strengthResult = this.passwordGenerationService.passwordStrength(this.masterPassword,
|
this.i18nService.t("no"),
|
||||||
this.getPasswordStrengthUserInput());
|
"warning"
|
||||||
this.masterPasswordScore = strengthResult == null ? null : strengthResult.score;
|
);
|
||||||
}, 300);
|
if (!result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async logOut() {
|
return true;
|
||||||
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() {
|
updatePasswordStrength() {
|
||||||
let userInput: string[] = [];
|
if (this.masterPasswordStrengthTimeout != null) {
|
||||||
const atPosition = this.email.indexOf('@');
|
clearTimeout(this.masterPasswordStrengthTimeout);
|
||||||
if (atPosition > -1) {
|
|
||||||
userInput = userInput.concat(this.email.substr(0, atPosition).trim().toLowerCase().split(/[^A-Za-z0-9]/));
|
|
||||||
}
|
|
||||||
return userInput;
|
|
||||||
}
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,95 +1,94 @@
|
||||||
import {
|
import { Directive, EventEmitter, Input, Output } from "@angular/core";
|
||||||
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()
|
@Directive()
|
||||||
export class CiphersComponent {
|
export class CiphersComponent {
|
||||||
@Input() activeCipherId: string = null;
|
@Input() activeCipherId: string = null;
|
||||||
@Output() onCipherClicked = new EventEmitter<CipherView>();
|
@Output() onCipherClicked = new EventEmitter<CipherView>();
|
||||||
@Output() onCipherRightClicked = new EventEmitter<CipherView>();
|
@Output() onCipherRightClicked = new EventEmitter<CipherView>();
|
||||||
@Output() onAddCipher = new EventEmitter();
|
@Output() onAddCipher = new EventEmitter();
|
||||||
@Output() onAddCipherOptions = new EventEmitter();
|
@Output() onAddCipherOptions = new EventEmitter();
|
||||||
|
|
||||||
loaded: boolean = false;
|
loaded: boolean = false;
|
||||||
ciphers: CipherView[] = [];
|
ciphers: CipherView[] = [];
|
||||||
searchText: string;
|
searchText: string;
|
||||||
searchPlaceholder: string = null;
|
searchPlaceholder: string = null;
|
||||||
filter: (cipher: CipherView) => boolean = null;
|
filter: (cipher: CipherView) => boolean = null;
|
||||||
deleted: boolean = false;
|
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) {
|
async load(filter: (cipher: CipherView) => boolean = null, deleted: boolean = false) {
|
||||||
this.deleted = deleted || false;
|
this.deleted = deleted || false;
|
||||||
await this.applyFilter(filter);
|
await this.applyFilter(filter);
|
||||||
this.loaded = true;
|
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);
|
||||||
}
|
}
|
||||||
|
if (timeout == null) {
|
||||||
async reload(filter: (cipher: CipherView) => boolean = null, deleted: boolean = false) {
|
await this.doSearch(indexedCiphers);
|
||||||
this.loaded = false;
|
return;
|
||||||
this.ciphers = [];
|
|
||||||
await this.load(filter, deleted);
|
|
||||||
}
|
}
|
||||||
|
this.searchPending = true;
|
||||||
|
this.searchTimeout = setTimeout(async () => {
|
||||||
|
await this.doSearch(indexedCiphers);
|
||||||
|
this.searchPending = false;
|
||||||
|
}, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
async refresh() {
|
selectCipher(cipher: CipherView) {
|
||||||
await this.reload(this.filter, this.deleted);
|
this.onCipherClicked.emit(cipher);
|
||||||
}
|
}
|
||||||
|
|
||||||
async applyFilter(filter: (cipher: CipherView) => boolean = null) {
|
rightClickCipher(cipher: CipherView) {
|
||||||
this.filter = filter;
|
this.onCipherRightClicked.emit(cipher);
|
||||||
await this.search(null);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async search(timeout: number = null, indexedCiphers?: CipherView[]) {
|
addCipher() {
|
||||||
this.searchPending = false;
|
this.onAddCipher.emit();
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectCipher(cipher: CipherView) {
|
addCipherOptions() {
|
||||||
this.onCipherClicked.emit(cipher);
|
this.onAddCipherOptions.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
rightClickCipher(cipher: CipherView) {
|
isSearching() {
|
||||||
this.onCipherRightClicked.emit(cipher);
|
return !this.searchPending && this.searchService.isSearchable(this.searchText);
|
||||||
}
|
}
|
||||||
|
|
||||||
addCipher() {
|
protected deletedFilter: (cipher: CipherView) => boolean = (c) => c.isDeleted === this.deleted;
|
||||||
this.onAddCipher.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
addCipherOptions() {
|
protected async doSearch(indexedCiphers?: CipherView[]) {
|
||||||
this.onAddCipherOptions.emit();
|
this.ciphers = await this.searchService.searchCiphers(
|
||||||
}
|
this.searchText,
|
||||||
|
[this.filter, this.deletedFilter],
|
||||||
isSearching() {
|
indexedCiphers
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,90 +1,94 @@
|
||||||
import {
|
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||||
Directive,
|
|
||||||
EventEmitter,
|
|
||||||
Input,
|
|
||||||
OnInit,
|
|
||||||
Output,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||||
import { CollectionService } from 'jslib-common/abstractions/collection.service';
|
import { CollectionService } from "jslib-common/abstractions/collection.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
|
||||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||||
import { CollectionView } from 'jslib-common/models/view/collectionView';
|
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()
|
@Directive()
|
||||||
export class CollectionsComponent implements OnInit {
|
export class CollectionsComponent implements OnInit {
|
||||||
@Input() cipherId: string;
|
@Input() cipherId: string;
|
||||||
@Input() allowSelectNone = false;
|
@Input() allowSelectNone = false;
|
||||||
@Output() onSavedCollections = new EventEmitter();
|
@Output() onSavedCollections = new EventEmitter();
|
||||||
|
|
||||||
formPromise: Promise<any>;
|
formPromise: Promise<any>;
|
||||||
cipher: CipherView;
|
cipher: CipherView;
|
||||||
collectionIds: string[];
|
collectionIds: string[];
|
||||||
collections: CollectionView[] = [];
|
collections: CollectionView[] = [];
|
||||||
|
|
||||||
protected cipherDomain: Cipher;
|
protected cipherDomain: Cipher;
|
||||||
|
|
||||||
constructor(protected collectionService: CollectionService, protected platformUtilsService: PlatformUtilsService,
|
constructor(
|
||||||
protected i18nService: I18nService, protected cipherService: CipherService, private logService: LogService) { }
|
protected collectionService: CollectionService,
|
||||||
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
|
protected i18nService: I18nService,
|
||||||
|
protected cipherService: CipherService,
|
||||||
|
private logService: LogService
|
||||||
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
await this.load();
|
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() {
|
async submit() {
|
||||||
this.cipherDomain = await this.loadCipher();
|
const selectedCollectionIds = this.collections
|
||||||
this.collectionIds = this.loadCipherCollections();
|
.filter((c) => !!(c as any).checked)
|
||||||
this.cipher = await this.cipherDomain.decrypt();
|
.map((c) => c.id);
|
||||||
this.collections = await this.loadCollections();
|
if (!this.allowSelectNone && selectedCollectionIds.length === 0) {
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
this.collections.forEach(c => (c as any).checked = false);
|
"error",
|
||||||
if (this.collectionIds != null) {
|
this.i18nService.t("errorOccurred"),
|
||||||
this.collections.forEach(c => {
|
this.i18nService.t("selectOneCollection")
|
||||||
(c as any).checked = this.collectionIds != null && this.collectionIds.indexOf(c.id) > -1;
|
);
|
||||||
});
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
this.cipherDomain.collectionIds = selectedCollectionIds;
|
||||||
async submit() {
|
try {
|
||||||
const selectedCollectionIds = this.collections
|
this.formPromise = this.saveCollections();
|
||||||
.filter(c => !!(c as any).checked)
|
await this.formPromise;
|
||||||
.map(c => c.id);
|
this.onSavedCollections.emit();
|
||||||
if (!this.allowSelectNone && selectedCollectionIds.length === 0) {
|
this.platformUtilsService.showToast("success", null, this.i18nService.t("editedItem"));
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
} catch (e) {
|
||||||
this.i18nService.t('selectOneCollection'));
|
this.logService.error(e);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected loadCipher() {
|
protected loadCipher() {
|
||||||
return this.cipherService.get(this.cipherId);
|
return this.cipherService.get(this.cipherId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected loadCipherCollections() {
|
protected loadCipherCollections() {
|
||||||
return this.cipherDomain.collectionIds;
|
return this.cipherDomain.collectionIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async loadCollections() {
|
protected async loadCollections() {
|
||||||
const allCollections = await this.collectionService.getAllDecrypted();
|
const allCollections = await this.collectionService.getAllDecrypted();
|
||||||
return allCollections.filter(c => !c.readOnly && c.organizationId === this.cipher.organizationId);
|
return allCollections.filter(
|
||||||
}
|
(c) => !c.readOnly && c.organizationId === this.cipher.organizationId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
protected saveCollections() {
|
protected saveCollections() {
|
||||||
return this.cipherService.saveCollectionsWithServer(this.cipherDomain);
|
return this.cipherService.saveCollectionsWithServer(this.cipherDomain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,65 +1,63 @@
|
||||||
import {
|
import { Directive, EventEmitter, Output } from "@angular/core";
|
||||||
Directive,
|
|
||||||
EventEmitter,
|
|
||||||
Output,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class EnvironmentComponent {
|
export class EnvironmentComponent {
|
||||||
@Output() onSaved = new EventEmitter();
|
@Output() onSaved = new EventEmitter();
|
||||||
|
|
||||||
iconsUrl: string;
|
iconsUrl: string;
|
||||||
identityUrl: string;
|
identityUrl: string;
|
||||||
apiUrl: string;
|
apiUrl: string;
|
||||||
webVaultUrl: string;
|
webVaultUrl: string;
|
||||||
notificationsUrl: string;
|
notificationsUrl: string;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
showCustom = false;
|
showCustom = false;
|
||||||
|
|
||||||
constructor(protected platformUtilsService: PlatformUtilsService, protected environmentService: EnvironmentService,
|
constructor(
|
||||||
protected i18nService: I18nService) {
|
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 || '';
|
async submit() {
|
||||||
this.webVaultUrl = urls.webVault || '';
|
const resUrls = await this.environmentService.setUrls({
|
||||||
this.apiUrl = urls.api || '';
|
base: this.baseUrl,
|
||||||
this.identityUrl = urls.identity || '';
|
api: this.apiUrl,
|
||||||
this.iconsUrl = urls.icons || '';
|
identity: this.identityUrl,
|
||||||
this.notificationsUrl = urls.notifications || '';
|
webVault: this.webVaultUrl,
|
||||||
}
|
icons: this.iconsUrl,
|
||||||
|
notifications: this.notificationsUrl,
|
||||||
|
});
|
||||||
|
|
||||||
async submit() {
|
// re-set urls since service can change them, ex: prefixing https://
|
||||||
const resUrls = await this.environmentService.setUrls({
|
this.baseUrl = resUrls.base;
|
||||||
base: this.baseUrl,
|
this.apiUrl = resUrls.api;
|
||||||
api: this.apiUrl,
|
this.identityUrl = resUrls.identity;
|
||||||
identity: this.identityUrl,
|
this.webVaultUrl = resUrls.webVault;
|
||||||
webVault: this.webVaultUrl,
|
this.iconsUrl = resUrls.icons;
|
||||||
icons: this.iconsUrl,
|
this.notificationsUrl = resUrls.notifications;
|
||||||
notifications: this.notificationsUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
// re-set urls since service can change them, ex: prefixing https://
|
this.platformUtilsService.showToast("success", null, this.i18nService.t("environmentSaved"));
|
||||||
this.baseUrl = resUrls.base;
|
this.saved();
|
||||||
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'));
|
toggleCustom() {
|
||||||
this.saved();
|
this.showCustom = !this.showCustom;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleCustom() {
|
protected saved() {
|
||||||
this.showCustom = !this.showCustom;
|
this.onSaved.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected saved() {
|
|
||||||
this.onSaved.emit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,140 +1,156 @@
|
||||||
import {
|
import { Directive, EventEmitter, OnInit, Output } from "@angular/core";
|
||||||
Directive,
|
import { FormBuilder } from "@angular/forms";
|
||||||
EventEmitter,
|
|
||||||
OnInit,
|
|
||||||
Output,
|
|
||||||
} from '@angular/core';
|
|
||||||
import { FormBuilder } from '@angular/forms';
|
|
||||||
|
|
||||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { EventService } from 'jslib-common/abstractions/event.service';
|
import { EventService } from "jslib-common/abstractions/event.service";
|
||||||
import { ExportService } from 'jslib-common/abstractions/export.service';
|
import { ExportService } from "jslib-common/abstractions/export.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||||
import { UserVerificationService } from 'jslib-common/abstractions/userVerification.service';
|
import { UserVerificationService } from "jslib-common/abstractions/userVerification.service";
|
||||||
|
|
||||||
import { EventType } from 'jslib-common/enums/eventType';
|
import { EventType } from "jslib-common/enums/eventType";
|
||||||
import { PolicyType } from 'jslib-common/enums/policyType';
|
import { PolicyType } from "jslib-common/enums/policyType";
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class ExportComponent implements OnInit {
|
export class ExportComponent implements OnInit {
|
||||||
@Output() onSaved = new EventEmitter();
|
@Output() onSaved = new EventEmitter();
|
||||||
|
|
||||||
formPromise: Promise<string>;
|
formPromise: Promise<string>;
|
||||||
disabledByPolicy: boolean = false;
|
disabledByPolicy: boolean = false;
|
||||||
|
|
||||||
exportForm = this.fb.group({
|
exportForm = this.fb.group({
|
||||||
format: ['json'],
|
format: ["json"],
|
||||||
secret: [''],
|
secret: [""],
|
||||||
});
|
});
|
||||||
|
|
||||||
formatOptions = [
|
formatOptions = [
|
||||||
{ name: '.json', value: 'json' },
|
{ name: ".json", value: "json" },
|
||||||
{ name: '.csv', value: 'csv' },
|
{ name: ".csv", value: "csv" },
|
||||||
{ name: '.json (Encrypted)', value: 'encrypted_json' },
|
{ name: ".json (Encrypted)", value: "encrypted_json" },
|
||||||
];
|
];
|
||||||
|
|
||||||
constructor(protected cryptoService: CryptoService, protected i18nService: I18nService,
|
constructor(
|
||||||
protected platformUtilsService: PlatformUtilsService, protected exportService: ExportService,
|
protected cryptoService: CryptoService,
|
||||||
protected eventService: EventService, private policyService: PolicyService, protected win: Window,
|
protected i18nService: I18nService,
|
||||||
private logService: LogService, private userVerificationService: UserVerificationService,
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
private fb: FormBuilder) { }
|
protected exportService: ExportService,
|
||||||
|
protected eventService: EventService,
|
||||||
|
private policyService: PolicyService,
|
||||||
|
protected win: Window,
|
||||||
|
private logService: LogService,
|
||||||
|
private userVerificationService: UserVerificationService,
|
||||||
|
private fb: FormBuilder
|
||||||
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
await this.checkExportDisabled();
|
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() {
|
const acceptedWarning = await this.warningDialog();
|
||||||
this.disabledByPolicy = await this.policyService.policyAppliesToUser(PolicyType.DisablePersonalVaultExport);
|
if (!acceptedWarning) {
|
||||||
if (this.disabledByPolicy) {
|
return;
|
||||||
this.exportForm.disable();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get encryptedFormat() {
|
const secret = this.exportForm.get("secret").value;
|
||||||
return this.format === 'encrypted_json';
|
try {
|
||||||
|
await this.userVerificationService.verifyUser(secret);
|
||||||
|
} catch (e) {
|
||||||
|
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
try {
|
||||||
if (this.disabledByPolicy) {
|
this.formPromise = this.getExportData();
|
||||||
this.platformUtilsService.showToast('error', null, this.i18nService.t('personalVaultExportPolicyInEffect'));
|
const data = await this.formPromise;
|
||||||
return;
|
this.downloadFile(data);
|
||||||
}
|
this.saved();
|
||||||
|
await this.collectEvent();
|
||||||
const acceptedWarning = await this.warningDialog();
|
this.exportForm.get("secret").setValue("");
|
||||||
if (!acceptedWarning) {
|
} catch (e) {
|
||||||
return;
|
this.logService.error(e);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async warningDialog() {
|
async warningDialog() {
|
||||||
if (this.encryptedFormat) {
|
if (this.encryptedFormat) {
|
||||||
return await this.platformUtilsService.showDialog(
|
return await this.platformUtilsService.showDialog(
|
||||||
'<p>' + this.i18nService.t('encExportKeyWarningDesc') +
|
"<p>" +
|
||||||
'<p>' + this.i18nService.t('encExportAccountWarningDesc'),
|
this.i18nService.t("encExportKeyWarningDesc") +
|
||||||
this.i18nService.t('confirmVaultExport'), this.i18nService.t('exportVault'),
|
"<p>" +
|
||||||
this.i18nService.t('cancel'), 'warning',
|
this.i18nService.t("encExportAccountWarningDesc"),
|
||||||
true);
|
this.i18nService.t("confirmVaultExport"),
|
||||||
} else {
|
this.i18nService.t("exportVault"),
|
||||||
return await this.platformUtilsService.showDialog(
|
this.i18nService.t("cancel"),
|
||||||
this.i18nService.t('exportWarningDesc'),
|
"warning",
|
||||||
this.i18nService.t('confirmVaultExport'), this.i18nService.t('exportVault'),
|
true
|
||||||
this.i18nService.t('cancel'), 'warning');
|
);
|
||||||
}
|
} 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() {
|
protected saved() {
|
||||||
this.onSaved.emit();
|
this.onSaved.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getExportData() {
|
protected getExportData() {
|
||||||
return this.exportService.getExport(this.format);
|
return this.exportService.getExport(this.format);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getFileName(prefix?: string) {
|
protected getFileName(prefix?: string) {
|
||||||
let extension = this.format;
|
let extension = this.format;
|
||||||
if (this.format === 'encrypted_json') {
|
if (this.format === "encrypted_json") {
|
||||||
if (prefix == null) {
|
if (prefix == null) {
|
||||||
prefix = 'encrypted';
|
prefix = "encrypted";
|
||||||
} else {
|
} else {
|
||||||
prefix = 'encrypted_' + prefix;
|
prefix = "encrypted_" + prefix;
|
||||||
}
|
}
|
||||||
extension = 'json';
|
extension = "json";
|
||||||
}
|
|
||||||
return this.exportService.getFileName(prefix, extension);
|
|
||||||
}
|
}
|
||||||
|
return this.exportService.getFileName(prefix, extension);
|
||||||
|
}
|
||||||
|
|
||||||
protected async collectEvent(): Promise<any> {
|
protected async collectEvent(): Promise<any> {
|
||||||
await this.eventService.collect(EventType.User_ClientExportedVault);
|
await this.eventService.collect(EventType.User_ClientExportedVault);
|
||||||
}
|
}
|
||||||
|
|
||||||
get format() {
|
get format() {
|
||||||
return this.exportForm.get('format').value;
|
return this.exportForm.get("format").value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private downloadFile(csv: string): void {
|
private downloadFile(csv: string): void {
|
||||||
const fileName = this.getFileName();
|
const fileName = this.getFileName();
|
||||||
this.platformUtilsService.saveFile(this.win, csv, { type: 'text/plain' }, fileName);
|
this.platformUtilsService.saveFile(this.win, csv, { type: "text/plain" }, fileName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,89 +1,97 @@
|
||||||
import {
|
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||||
Directive,
|
|
||||||
EventEmitter,
|
|
||||||
Input,
|
|
||||||
OnInit,
|
|
||||||
Output,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { FolderService } from 'jslib-common/abstractions/folder.service';
|
import { FolderService } from "jslib-common/abstractions/folder.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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()
|
@Directive()
|
||||||
export class FolderAddEditComponent implements OnInit {
|
export class FolderAddEditComponent implements OnInit {
|
||||||
@Input() folderId: string;
|
@Input() folderId: string;
|
||||||
@Output() onSavedFolder = new EventEmitter<FolderView>();
|
@Output() onSavedFolder = new EventEmitter<FolderView>();
|
||||||
@Output() onDeletedFolder = new EventEmitter<FolderView>();
|
@Output() onDeletedFolder = new EventEmitter<FolderView>();
|
||||||
|
|
||||||
editMode: boolean = false;
|
editMode: boolean = false;
|
||||||
folder: FolderView = new FolderView();
|
folder: FolderView = new FolderView();
|
||||||
title: string;
|
title: string;
|
||||||
formPromise: Promise<any>;
|
formPromise: Promise<any>;
|
||||||
deletePromise: Promise<any>;
|
deletePromise: Promise<any>;
|
||||||
|
|
||||||
constructor(protected folderService: FolderService, protected i18nService: I18nService,
|
constructor(
|
||||||
protected platformUtilsService: PlatformUtilsService, private logService: LogService) { }
|
protected folderService: FolderService,
|
||||||
|
protected i18nService: I18nService,
|
||||||
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
|
private logService: LogService
|
||||||
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
await this.init();
|
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> {
|
try {
|
||||||
if (this.folder.name == null || this.folder.name === '') {
|
const folder = await this.folderService.encrypt(this.folder);
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
this.formPromise = this.folderService.saveWithServer(folder);
|
||||||
this.i18nService.t('nameRequired'));
|
await this.formPromise;
|
||||||
return false;
|
this.platformUtilsService.showToast(
|
||||||
}
|
"success",
|
||||||
|
null,
|
||||||
try {
|
this.i18nService.t(this.editMode ? "editedFolder" : "addedFolder")
|
||||||
const folder = await this.folderService.encrypt(this.folder);
|
);
|
||||||
this.formPromise = this.folderService.saveWithServer(folder);
|
this.onSavedFolder.emit(this.folder);
|
||||||
await this.formPromise;
|
return true;
|
||||||
this.platformUtilsService.showToast('success', null,
|
} catch (e) {
|
||||||
this.i18nService.t(this.editMode ? 'editedFolder' : 'addedFolder'));
|
this.logService.error(e);
|
||||||
this.onSavedFolder.emit(this.folder);
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
this.logService.error(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(): Promise<boolean> {
|
return false;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
async delete(): Promise<boolean> {
|
||||||
this.deletePromise = this.folderService.deleteWithServer(this.folder.id);
|
const confirmed = await this.platformUtilsService.showDialog(
|
||||||
await this.deletePromise;
|
this.i18nService.t("deleteFolderConfirmation"),
|
||||||
this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedFolder'));
|
this.i18nService.t("deleteFolder"),
|
||||||
this.onDeletedFolder.emit(this.folder);
|
this.i18nService.t("yes"),
|
||||||
} catch (e) {
|
this.i18nService.t("no"),
|
||||||
this.logService.error(e);
|
"warning"
|
||||||
}
|
);
|
||||||
|
if (!confirmed) {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async init() {
|
try {
|
||||||
this.editMode = this.folderId != null;
|
this.deletePromise = this.folderService.deleteWithServer(this.folder.id);
|
||||||
|
await this.deletePromise;
|
||||||
if (this.editMode) {
|
this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedFolder"));
|
||||||
this.editMode = true;
|
this.onDeletedFolder.emit(this.folder);
|
||||||
this.title = this.i18nService.t('editFolder');
|
} catch (e) {
|
||||||
const folder = await this.folderService.get(this.folderId);
|
this.logService.error(e);
|
||||||
this.folder = await folder.decrypt();
|
|
||||||
} else {
|
|
||||||
this.title = this.i18nService.t('addFolder');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,162 +1,160 @@
|
||||||
import {
|
import { Directive, EventEmitter, Input, Output } from "@angular/core";
|
||||||
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 { CollectionView } from "jslib-common/models/view/collectionView";
|
||||||
import { FolderView } from 'jslib-common/models/view/folderView';
|
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 { CollectionService } from "jslib-common/abstractions/collection.service";
|
||||||
import { FolderService } from 'jslib-common/abstractions/folder.service';
|
import { FolderService } from "jslib-common/abstractions/folder.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class GroupingsComponent {
|
export class GroupingsComponent {
|
||||||
@Input() showFolders = true;
|
@Input() showFolders = true;
|
||||||
@Input() showCollections = true;
|
@Input() showCollections = true;
|
||||||
@Input() showFavorites = true;
|
@Input() showFavorites = true;
|
||||||
@Input() showTrash = true;
|
@Input() showTrash = true;
|
||||||
|
|
||||||
@Output() onAllClicked = new EventEmitter();
|
@Output() onAllClicked = new EventEmitter();
|
||||||
@Output() onFavoritesClicked = new EventEmitter();
|
@Output() onFavoritesClicked = new EventEmitter();
|
||||||
@Output() onTrashClicked = new EventEmitter();
|
@Output() onTrashClicked = new EventEmitter();
|
||||||
@Output() onCipherTypeClicked = new EventEmitter<CipherType>();
|
@Output() onCipherTypeClicked = new EventEmitter<CipherType>();
|
||||||
@Output() onFolderClicked = new EventEmitter<FolderView>();
|
@Output() onFolderClicked = new EventEmitter<FolderView>();
|
||||||
@Output() onAddFolder = new EventEmitter();
|
@Output() onAddFolder = new EventEmitter();
|
||||||
@Output() onEditFolder = new EventEmitter<FolderView>();
|
@Output() onEditFolder = new EventEmitter<FolderView>();
|
||||||
@Output() onCollectionClicked = new EventEmitter<CollectionView>();
|
@Output() onCollectionClicked = new EventEmitter<CollectionView>();
|
||||||
|
|
||||||
folders: FolderView[];
|
folders: FolderView[];
|
||||||
nestedFolders: TreeNode<FolderView>[];
|
nestedFolders: TreeNode<FolderView>[];
|
||||||
collections: CollectionView[];
|
collections: CollectionView[];
|
||||||
nestedCollections: TreeNode<CollectionView>[];
|
nestedCollections: TreeNode<CollectionView>[];
|
||||||
loaded: boolean = false;
|
loaded: boolean = false;
|
||||||
cipherType = CipherType;
|
cipherType = CipherType;
|
||||||
selectedAll: boolean = false;
|
selectedAll: boolean = false;
|
||||||
selectedFavorites: boolean = false;
|
selectedFavorites: boolean = false;
|
||||||
selectedTrash: boolean = false;
|
selectedTrash: boolean = false;
|
||||||
selectedType: CipherType = null;
|
selectedType: CipherType = null;
|
||||||
selectedFolder: boolean = false;
|
selectedFolder: boolean = false;
|
||||||
selectedFolderId: string = null;
|
selectedFolderId: string = null;
|
||||||
selectedCollectionId: string = null;
|
selectedCollectionId: string = null;
|
||||||
|
|
||||||
private collapsedGroupings: Set<string>;
|
private collapsedGroupings: Set<string>;
|
||||||
|
|
||||||
constructor(protected collectionService: CollectionService, protected folderService: FolderService,
|
constructor(
|
||||||
protected stateService: StateService) { }
|
protected collectionService: CollectionService,
|
||||||
|
protected folderService: FolderService,
|
||||||
|
protected stateService: StateService
|
||||||
|
) {}
|
||||||
|
|
||||||
async load(setLoaded = true) {
|
async load(setLoaded = true) {
|
||||||
const collapsedGroupings = await this.stateService.getCollapsedGroupings();
|
const collapsedGroupings = await this.stateService.getCollapsedGroupings();
|
||||||
if (collapsedGroupings == null) {
|
if (collapsedGroupings == null) {
|
||||||
this.collapsedGroupings = new Set<string>();
|
this.collapsedGroupings = new Set<string>();
|
||||||
} else {
|
} else {
|
||||||
this.collapsedGroupings = new Set(collapsedGroupings);
|
this.collapsedGroupings = new Set(collapsedGroupings);
|
||||||
}
|
|
||||||
|
|
||||||
await this.loadFolders();
|
|
||||||
await this.loadCollections();
|
|
||||||
|
|
||||||
if (setLoaded) {
|
|
||||||
this.loaded = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadCollections(organizationId?: string) {
|
await this.loadFolders();
|
||||||
if (!this.showCollections) {
|
await this.loadCollections();
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadFolders() {
|
if (setLoaded) {
|
||||||
if (!this.showFolders) {
|
this.loaded = true;
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.folders = await this.folderService.getAllDecrypted();
|
|
||||||
this.nestedFolders = await this.folderService.getAllNested();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
selectAll() {
|
async loadCollections(organizationId?: string) {
|
||||||
this.clearSelections();
|
if (!this.showCollections) {
|
||||||
this.selectedAll = true;
|
return;
|
||||||
this.onAllClicked.emit();
|
|
||||||
}
|
}
|
||||||
|
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() {
|
async loadFolders() {
|
||||||
this.clearSelections();
|
if (!this.showFolders) {
|
||||||
this.selectedFavorites = true;
|
return;
|
||||||
this.onFavoritesClicked.emit();
|
|
||||||
}
|
}
|
||||||
|
this.folders = await this.folderService.getAllDecrypted();
|
||||||
|
this.nestedFolders = await this.folderService.getAllNested();
|
||||||
|
}
|
||||||
|
|
||||||
selectTrash() {
|
selectAll() {
|
||||||
this.clearSelections();
|
this.clearSelections();
|
||||||
this.selectedTrash = true;
|
this.selectedAll = true;
|
||||||
this.onTrashClicked.emit();
|
this.onAllClicked.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
selectType(type: CipherType) {
|
selectFavorites() {
|
||||||
this.clearSelections();
|
this.clearSelections();
|
||||||
this.selectedType = type;
|
this.selectedFavorites = true;
|
||||||
this.onCipherTypeClicked.emit(type);
|
this.onFavoritesClicked.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
selectFolder(folder: FolderView) {
|
selectTrash() {
|
||||||
this.clearSelections();
|
this.clearSelections();
|
||||||
this.selectedFolder = true;
|
this.selectedTrash = true;
|
||||||
this.selectedFolderId = folder.id;
|
this.onTrashClicked.emit();
|
||||||
this.onFolderClicked.emit(folder);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
addFolder() {
|
selectType(type: CipherType) {
|
||||||
this.onAddFolder.emit();
|
this.clearSelections();
|
||||||
}
|
this.selectedType = type;
|
||||||
|
this.onCipherTypeClicked.emit(type);
|
||||||
|
}
|
||||||
|
|
||||||
editFolder(folder: FolderView) {
|
selectFolder(folder: FolderView) {
|
||||||
this.onEditFolder.emit(folder);
|
this.clearSelections();
|
||||||
}
|
this.selectedFolder = true;
|
||||||
|
this.selectedFolderId = folder.id;
|
||||||
|
this.onFolderClicked.emit(folder);
|
||||||
|
}
|
||||||
|
|
||||||
selectCollection(collection: CollectionView) {
|
addFolder() {
|
||||||
this.clearSelections();
|
this.onAddFolder.emit();
|
||||||
this.selectedCollectionId = collection.id;
|
}
|
||||||
this.onCollectionClicked.emit(collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearSelections() {
|
editFolder(folder: FolderView) {
|
||||||
this.selectedAll = false;
|
this.onEditFolder.emit(folder);
|
||||||
this.selectedFavorites = false;
|
}
|
||||||
this.selectedTrash = false;
|
|
||||||
this.selectedType = null;
|
|
||||||
this.selectedFolder = false;
|
|
||||||
this.selectedFolderId = null;
|
|
||||||
this.selectedCollectionId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async collapse(grouping: FolderView | CollectionView, idPrefix = '') {
|
selectCollection(collection: CollectionView) {
|
||||||
if (grouping.id == null) {
|
this.clearSelections();
|
||||||
return;
|
this.selectedCollectionId = collection.id;
|
||||||
}
|
this.onCollectionClicked.emit(collection);
|
||||||
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 = '') {
|
clearSelections() {
|
||||||
return this.collapsedGroupings.has(idPrefix + grouping.id);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
|
||||||
export class HintComponent {
|
export class HintComponent {
|
||||||
email: string = '';
|
email: string = "";
|
||||||
formPromise: Promise<any>;
|
formPromise: Promise<any>;
|
||||||
|
|
||||||
protected successRoute = 'login';
|
protected successRoute = "login";
|
||||||
protected onSuccessfulSubmit: () => void;
|
protected onSuccessfulSubmit: () => void;
|
||||||
|
|
||||||
constructor(protected router: Router, protected i18nService: I18nService,
|
constructor(
|
||||||
protected apiService: ApiService, protected platformUtilsService: PlatformUtilsService,
|
protected router: Router,
|
||||||
private logService: LogService) { }
|
protected i18nService: I18nService,
|
||||||
|
protected apiService: ApiService,
|
||||||
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
|
private logService: LogService
|
||||||
|
) {}
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
if (this.email == null || this.email === '') {
|
if (this.email == null || this.email === "") {
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
this.platformUtilsService.showToast(
|
||||||
this.i18nService.t('emailRequired'));
|
"error",
|
||||||
return;
|
this.i18nService.t("errorOccurred"),
|
||||||
}
|
this.i18nService.t("emailRequired")
|
||||||
if (this.email.indexOf('@') === -1) {
|
);
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
return;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div class="icon" aria-hidden="true">
|
<div class="icon" aria-hidden="true">
|
||||||
<img [src]="image" appFallbackSrc="{{fallbackImage}}" *ngIf="imageEnabled && image" alt="" />
|
<img [src]="image" appFallbackSrc="{{ fallbackImage }}" *ngIf="imageEnabled && image" alt="" />
|
||||||
<i class="fa fa-fw fa-lg {{icon}}" *ngIf="!imageEnabled || !image"></i>
|
<i class="fa fa-fw fa-lg {{ icon }}" *ngIf="!imageEnabled || !image"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,105 +1,105 @@
|
||||||
import {
|
import { Component, Input, OnChanges } from "@angular/core";
|
||||||
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 { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.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 = {
|
const IconMap: any = {
|
||||||
'fa-globe': String.fromCharCode(0xf0ac),
|
"fa-globe": String.fromCharCode(0xf0ac),
|
||||||
'fa-sticky-note-o': String.fromCharCode(0xf24a),
|
"fa-sticky-note-o": String.fromCharCode(0xf24a),
|
||||||
'fa-id-card-o': String.fromCharCode(0xf2c3),
|
"fa-id-card-o": String.fromCharCode(0xf2c3),
|
||||||
'fa-credit-card': String.fromCharCode(0xf09d),
|
"fa-credit-card": String.fromCharCode(0xf09d),
|
||||||
'fa-android': String.fromCharCode(0xf17b),
|
"fa-android": String.fromCharCode(0xf17b),
|
||||||
'fa-apple': String.fromCharCode(0xf179),
|
"fa-apple": String.fromCharCode(0xf179),
|
||||||
};
|
};
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-vault-icon',
|
selector: "app-vault-icon",
|
||||||
templateUrl: 'icon.component.html',
|
templateUrl: "icon.component.html",
|
||||||
})
|
})
|
||||||
export class IconComponent implements OnChanges {
|
export class IconComponent implements OnChanges {
|
||||||
@Input() cipher: CipherView;
|
@Input() cipher: CipherView;
|
||||||
icon: string;
|
icon: string;
|
||||||
image: string;
|
image: string;
|
||||||
fallbackImage: string;
|
fallbackImage: string;
|
||||||
imageEnabled: boolean;
|
imageEnabled: boolean;
|
||||||
|
|
||||||
private iconsUrl: string;
|
private iconsUrl: string;
|
||||||
|
|
||||||
constructor(environmentService: EnvironmentService, private stateService: StateService) {
|
constructor(environmentService: EnvironmentService, private stateService: StateService) {
|
||||||
this.iconsUrl = environmentService.getIconsUrl();
|
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() {
|
private setLoginIcon() {
|
||||||
// Components may be re-used when using cdk-virtual-scroll. Which puts the component in a weird state,
|
if (this.cipher.login.uri) {
|
||||||
// to avoid this we reset all state variables.
|
let hostnameUri = this.cipher.login.uri;
|
||||||
|
let isWebsite = false;
|
||||||
|
|
||||||
|
if (hostnameUri.indexOf("androidapp://") === 0) {
|
||||||
|
this.icon = "fa-android";
|
||||||
this.image = null;
|
this.image = null;
|
||||||
this.fallbackImage = null;
|
} else if (hostnameUri.indexOf("iosapp://") === 0) {
|
||||||
this.imageEnabled = !(await this.stateService.getDisableFavicon());
|
this.icon = "fa-apple";
|
||||||
this.load();
|
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 {
|
if (this.imageEnabled && isWebsite) {
|
||||||
return IconMap[this.icon];
|
try {
|
||||||
}
|
this.image = this.iconsUrl + "/" + Utils.getHostname(hostnameUri) + "/icon.png";
|
||||||
|
this.fallbackImage = "images/fa-globe.png";
|
||||||
protected load() {
|
} catch (e) {
|
||||||
switch (this.cipher.type) {
|
// Ignore error since the fallback icon will be shown if image is null.
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.image = null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,216 +1,279 @@
|
||||||
import { Directive, NgZone, OnInit } from '@angular/core';
|
import { Directive, 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 { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
|
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||||
|
|
||||||
import { EncString } from 'jslib-common/models/domain/encString';
|
import { EncString } from "jslib-common/models/domain/encString";
|
||||||
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
|
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 { HashPurpose } from "jslib-common/enums/hashPurpose";
|
||||||
import { KeySuffixOptions } from 'jslib-common/enums/keySuffixOptions';
|
import { KeySuffixOptions } from "jslib-common/enums/keySuffixOptions";
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class LockComponent implements OnInit {
|
export class LockComponent implements OnInit {
|
||||||
masterPassword: string = '';
|
masterPassword: string = "";
|
||||||
pin: string = '';
|
pin: string = "";
|
||||||
showPassword: boolean = false;
|
showPassword: boolean = false;
|
||||||
email: string;
|
email: string;
|
||||||
pinLock: boolean = false;
|
pinLock: boolean = false;
|
||||||
webVaultHostname: string = '';
|
webVaultHostname: string = "";
|
||||||
formPromise: Promise<any>;
|
formPromise: Promise<any>;
|
||||||
supportsBiometric: boolean;
|
supportsBiometric: boolean;
|
||||||
biometricLock: boolean;
|
biometricLock: boolean;
|
||||||
biometricText: string;
|
biometricText: string;
|
||||||
hideInput: boolean;
|
hideInput: boolean;
|
||||||
|
|
||||||
protected successRoute: string = 'vault';
|
protected successRoute: string = "vault";
|
||||||
protected onSuccessfulSubmit: () => Promise<void>;
|
protected onSuccessfulSubmit: () => Promise<void>;
|
||||||
|
|
||||||
private invalidPinAttempts = 0;
|
private invalidPinAttempts = 0;
|
||||||
private pinSet: [boolean, boolean];
|
private pinSet: [boolean, boolean];
|
||||||
|
|
||||||
constructor(protected router: Router, protected i18nService: I18nService,
|
constructor(
|
||||||
protected platformUtilsService: PlatformUtilsService, protected messagingService: MessagingService,
|
protected router: Router,
|
||||||
protected cryptoService: CryptoService, protected vaultTimeoutService: VaultTimeoutService,
|
protected i18nService: I18nService,
|
||||||
protected environmentService: EnvironmentService, protected stateService: StateService,
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
protected apiService: ApiService, private logService: LogService,
|
protected messagingService: MessagingService,
|
||||||
private keyConnectorService: KeyConnectorService, protected ngZone: NgZone) { }
|
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() {
|
async ngOnInit() {
|
||||||
this.stateService.activeAccount.subscribe(async _userId => {
|
this.stateService.activeAccount.subscribe(async (_userId) => {
|
||||||
await this.load();
|
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() {
|
const kdf = await this.stateService.getKdfType();
|
||||||
if (this.pinLock && (this.pin == null || this.pin === '')) {
|
const kdfIterations = await this.stateService.getKdfIterations();
|
||||||
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();
|
if (this.pinLock) {
|
||||||
const kdfIterations = await this.stateService.getKdfIterations();
|
let failed = true;
|
||||||
|
try {
|
||||||
if (this.pinLock) {
|
if (this.pinSet[0]) {
|
||||||
let failed = true;
|
const key = await this.cryptoService.makeKeyFromPin(
|
||||||
try {
|
this.pin,
|
||||||
if (this.pinSet[0]) {
|
this.email,
|
||||||
const key = await this.cryptoService.makeKeyFromPin(this.pin, this.email, kdf, kdfIterations,
|
kdf,
|
||||||
await this.stateService.getDecryptedPinProtected());
|
kdfIterations,
|
||||||
const encKey = await this.cryptoService.getEncKey(key);
|
await this.stateService.getDecryptedPinProtected()
|
||||||
const protectedPin = await this.stateService.getProtectedPin();
|
);
|
||||||
const decPin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), encKey);
|
const encKey = await this.cryptoService.getEncKey(key);
|
||||||
failed = decPin !== this.pin;
|
const protectedPin = await this.stateService.getProtectedPin();
|
||||||
if (!failed) {
|
const decPin = await this.cryptoService.decryptToUtf8(
|
||||||
await this.setKeyAndContinue(key);
|
new EncString(protectedPin),
|
||||||
}
|
encKey
|
||||||
} else {
|
);
|
||||||
const key = await this.cryptoService.makeKeyFromPin(this.pin, this.email, kdf, kdfIterations);
|
failed = decPin !== this.pin;
|
||||||
failed = false;
|
if (!failed) {
|
||||||
await this.setKeyAndContinue(key);
|
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 {
|
} else {
|
||||||
const key = await this.cryptoService.makeKey(this.masterPassword, this.email, kdf, kdfIterations);
|
const key = await this.cryptoService.makeKeyFromPin(
|
||||||
const storedKeyHash = await this.cryptoService.getKeyHash();
|
this.pin,
|
||||||
|
this.email,
|
||||||
let passwordValid = false;
|
kdf,
|
||||||
|
kdfIterations
|
||||||
if (storedKeyHash != null) {
|
);
|
||||||
passwordValid = await this.cryptoService.compareAndUpdateKeyHash(this.masterPassword, key);
|
failed = false;
|
||||||
} else {
|
await this.setKeyAndContinue(key);
|
||||||
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'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} 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 success = (await this.cryptoService.getKey(KeySuffixOptions.Biometric)) != null;
|
||||||
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('logOutConfirmation'),
|
|
||||||
this.i18nService.t('logOut'), this.i18nService.t('logOut'), this.i18nService.t('cancel'));
|
if (success) {
|
||||||
if (confirmed) {
|
await this.doContinue();
|
||||||
this.messagingService.send('logout');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async unlockBiometric(): Promise<boolean> {
|
return success;
|
||||||
if (!this.biometricLock) {
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
private async setKeyAndContinue(key: SymmetricCryptoKey) {
|
||||||
await this.doContinue();
|
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() {
|
const webVaultUrl = this.environmentService.getWebVaultUrl();
|
||||||
this.showPassword = !this.showPassword;
|
const vaultUrl =
|
||||||
const input = document.getElementById(this.pinLock ? 'pin' : 'masterPassword');
|
webVaultUrl === "https://vault.bitwarden.com" ? "https://bitwarden.com" : webVaultUrl;
|
||||||
if (this.ngZone.isStable) {
|
this.webVaultHostname = Utils.getHostname(vaultUrl);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,164 +1,186 @@
|
||||||
import {
|
import { Directive, Input, NgZone, OnInit } from "@angular/core";
|
||||||
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 { AuthService } from "jslib-common/abstractions/auth.service";
|
||||||
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
|
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.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()
|
@Directive()
|
||||||
export class LoginComponent extends CaptchaProtectedComponent implements OnInit {
|
export class LoginComponent extends CaptchaProtectedComponent implements OnInit {
|
||||||
@Input() email: string = '';
|
@Input() email: string = "";
|
||||||
@Input() rememberEmail = true;
|
@Input() rememberEmail = true;
|
||||||
|
|
||||||
masterPassword: string = '';
|
masterPassword: string = "";
|
||||||
showPassword: boolean = false;
|
showPassword: boolean = false;
|
||||||
formPromise: Promise<AuthResult>;
|
formPromise: Promise<AuthResult>;
|
||||||
onSuccessfulLogin: () => Promise<any>;
|
onSuccessfulLogin: () => Promise<any>;
|
||||||
onSuccessfulLoginNavigate: () => Promise<any>;
|
onSuccessfulLoginNavigate: () => Promise<any>;
|
||||||
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>;
|
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>;
|
||||||
onSuccessfulLoginForceResetNavigate: () => Promise<any>;
|
onSuccessfulLoginForceResetNavigate: () => Promise<any>;
|
||||||
|
|
||||||
protected twoFactorRoute = '2fa';
|
protected twoFactorRoute = "2fa";
|
||||||
protected successRoute = 'vault';
|
protected successRoute = "vault";
|
||||||
protected forcePasswordResetRoute = 'update-temp-password';
|
protected forcePasswordResetRoute = "update-temp-password";
|
||||||
|
|
||||||
constructor(protected authService: AuthService, protected router: Router,
|
constructor(
|
||||||
platformUtilsService: PlatformUtilsService, i18nService: I18nService,
|
protected authService: AuthService,
|
||||||
protected stateService: StateService, environmentService: EnvironmentService,
|
protected router: Router,
|
||||||
protected passwordGenerationService: PasswordGenerationService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
protected cryptoFunctionService: CryptoFunctionService, protected logService: LogService,
|
i18nService: I18nService,
|
||||||
protected ngZone: NgZone) {
|
protected stateService: StateService,
|
||||||
super(environmentService, i18nService, platformUtilsService);
|
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() {
|
try {
|
||||||
if (this.email == null || this.email === '') {
|
this.formPromise = this.authService.logIn(this.email, this.masterPassword, this.captchaToken);
|
||||||
this.email = await this.stateService.getRememberedEmail();
|
const response = await this.formPromise;
|
||||||
if (this.email == null) {
|
if (this.rememberEmail) {
|
||||||
this.email = '';
|
await this.stateService.setRememberedEmail(this.email);
|
||||||
}
|
} else {
|
||||||
}
|
await this.stateService.setRememberedEmail(null);
|
||||||
this.rememberEmail = await this.stateService.getRememberedEmail() != null;
|
}
|
||||||
if (Utils.isBrowser && !Utils.isNode) {
|
if (this.handleCaptchaRequired(response)) {
|
||||||
this.focusInput();
|
return;
|
||||||
}
|
} else if (response.twoFactor) {
|
||||||
}
|
if (this.onSuccessfulLoginTwoFactorNavigate != null) {
|
||||||
|
this.onSuccessfulLoginTwoFactorNavigate();
|
||||||
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();
|
|
||||||
} else {
|
} 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) {
|
togglePassword() {
|
||||||
// Generate necessary sso params
|
this.showPassword = !this.showPassword;
|
||||||
const passwordOptions: any = {
|
if (this.ngZone.isStable) {
|
||||||
type: 'password',
|
document.getElementById("masterPassword").focus();
|
||||||
length: 64,
|
} else {
|
||||||
uppercase: true,
|
this.ngZone.onStable
|
||||||
lowercase: true,
|
.pipe(take(1))
|
||||||
numbers: true,
|
.subscribe(() => document.getElementById("masterPassword").focus());
|
||||||
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() {
|
async launchSsoBrowser(clientId: string, ssoRedirectUri: string) {
|
||||||
document.getElementById(this.email == null || this.email === '' ? 'email' : 'masterPassword').focus();
|
// 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,76 +1,80 @@
|
||||||
import {
|
import {
|
||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
Component,
|
Component,
|
||||||
ComponentRef,
|
ComponentRef,
|
||||||
ElementRef,
|
ElementRef,
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
Type,
|
Type,
|
||||||
ViewChild,
|
ViewChild,
|
||||||
ViewContainerRef
|
ViewContainerRef,
|
||||||
} from '@angular/core';
|
} from "@angular/core";
|
||||||
|
|
||||||
import {
|
import { ConfigurableFocusTrap, ConfigurableFocusTrapFactory } from "@angular/cdk/a11y";
|
||||||
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({
|
@Component({
|
||||||
selector: 'app-modal',
|
selector: "app-modal",
|
||||||
template: '<ng-template #modalContent></ng-template>',
|
template: "<ng-template #modalContent></ng-template>",
|
||||||
})
|
})
|
||||||
export class DynamicModalComponent implements AfterViewInit, OnDestroy {
|
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>;
|
childComponentType: Type<any>;
|
||||||
setComponentParameters: (component: any) => void;
|
setComponentParameters: (component: any) => void;
|
||||||
|
|
||||||
private focusTrap: ConfigurableFocusTrap;
|
private focusTrap: ConfigurableFocusTrap;
|
||||||
|
|
||||||
constructor(private modalService: ModalService, private cd: ChangeDetectorRef,
|
constructor(
|
||||||
private el: ElementRef<HTMLElement>, private focusTrapFactory: ConfigurableFocusTrapFactory,
|
private modalService: ModalService,
|
||||||
public modalRef: ModalRef) { }
|
private cd: ChangeDetectorRef,
|
||||||
|
private el: ElementRef<HTMLElement>,
|
||||||
|
private focusTrapFactory: ConfigurableFocusTrapFactory,
|
||||||
|
public modalRef: ModalRef
|
||||||
|
) {}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
this.loadChildComponent(this.childComponentType);
|
this.loadChildComponent(this.childComponentType);
|
||||||
if (this.setComponentParameters != null) {
|
if (this.setComponentParameters != null) {
|
||||||
this.setComponentParameters(this.componentRef.instance);
|
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
this.cd.detectChanges();
|
||||||
|
|
||||||
loadChildComponent(componentType: Type<any>) {
|
this.modalRef.created(this.el.nativeElement);
|
||||||
const componentFactory = this.modalService.resolveComponentFactory(componentType);
|
this.focusTrap = this.focusTrapFactory.create(
|
||||||
|
this.el.nativeElement.querySelector(".modal-dialog")
|
||||||
this.modalContentRef.clear();
|
);
|
||||||
this.componentRef = this.modalContentRef.createComponent(componentFactory);
|
if (this.el.nativeElement.querySelector("[appAutoFocus]") == null) {
|
||||||
|
this.focusTrap.focusFirstTabbableElementWhenReady();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
loadChildComponent(componentType: Type<any>) {
|
||||||
if (this.componentRef) {
|
const componentFactory = this.modalService.resolveComponentFactory(componentType);
|
||||||
this.componentRef.destroy();
|
|
||||||
}
|
|
||||||
this.focusTrap.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
close() {
|
this.modalContentRef.clear();
|
||||||
this.modalRef.close();
|
this.componentRef = this.modalContentRef.createComponent(componentFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
getFocus() {
|
ngOnDestroy() {
|
||||||
const autoFocusEl = this.el.nativeElement.querySelector('[appAutoFocus]') as HTMLElement;
|
if (this.componentRef) {
|
||||||
autoFocusEl?.focus();
|
this.componentRef.destroy();
|
||||||
}
|
}
|
||||||
|
this.focusTrap.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.modalRef.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
getFocus() {
|
||||||
|
const autoFocusEl = this.el.nativeElement.querySelector("[appAutoFocus]") as HTMLElement;
|
||||||
|
autoFocusEl?.focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,10 @@
|
||||||
import {
|
import { InjectFlags, InjectionToken, Injector, Type } from "@angular/core";
|
||||||
InjectFlags,
|
|
||||||
InjectionToken,
|
|
||||||
Injector,
|
|
||||||
Type
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
export class ModalInjector implements Injector {
|
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<T>(token: Type<T> | InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
|
||||||
get(token: any, notFoundValue?: any, flags?: any) {
|
get(token: any, notFoundValue?: any, flags?: any) {
|
||||||
return this._additionalTokens.get(token) ?? this._parentInjector.get<any>(token, notFoundValue);
|
return this._additionalTokens.get(token) ?? this._parentInjector.get<any>(token, notFoundValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,51 +1,50 @@
|
||||||
import { Observable, Subject } from 'rxjs';
|
import { Observable, Subject } from "rxjs";
|
||||||
import { first } from 'rxjs/operators';
|
import { first } from "rxjs/operators";
|
||||||
|
|
||||||
export class ModalRef {
|
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.
|
private readonly _onCreated = new Subject<HTMLElement>();
|
||||||
onClose: Observable<any>; // Initiated close.
|
private readonly _onClose = new Subject<any>();
|
||||||
onClosed: Observable<any>; // Modal was closed (Remove element from DOM)
|
private readonly _onClosed = new Subject<any>();
|
||||||
onShow: Observable<void>; // Start showing modal
|
private readonly _onShow = new Subject<void>();
|
||||||
onShown: Observable<void>; // Modal is fully visible
|
private readonly _onShown = new Subject<void>();
|
||||||
|
private lastResult: any;
|
||||||
|
|
||||||
private readonly _onCreated = new Subject<HTMLElement>();
|
constructor() {
|
||||||
private readonly _onClose = new Subject<any>();
|
this.onCreated = this._onCreated.asObservable();
|
||||||
private readonly _onClosed = new Subject<any>();
|
this.onClose = this._onClose.asObservable();
|
||||||
private readonly _onShow = new Subject<void>();
|
this.onClosed = this._onClosed.asObservable();
|
||||||
private readonly _onShown = new Subject<void>();
|
this.onShow = this._onShow.asObservable();
|
||||||
private lastResult: any;
|
this.onShown = this._onShow.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
show() {
|
||||||
this.onCreated = this._onCreated.asObservable();
|
this._onShow.next();
|
||||||
this.onClose = this._onClose.asObservable();
|
}
|
||||||
this.onClosed = this._onClosed.asObservable();
|
|
||||||
this.onShow = this._onShow.asObservable();
|
|
||||||
this.onShown = this._onShow.asObservable();
|
|
||||||
}
|
|
||||||
|
|
||||||
show() {
|
shown() {
|
||||||
this._onShow.next();
|
this._onShown.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
shown() {
|
close(result?: any) {
|
||||||
this._onShown.next();
|
this.lastResult = result;
|
||||||
}
|
this._onClose.next(result);
|
||||||
|
}
|
||||||
|
|
||||||
close(result?: any) {
|
closed() {
|
||||||
this.lastResult = result;
|
this._onClosed.next(this.lastResult);
|
||||||
this._onClose.next(result);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
closed() {
|
created(el: HTMLElement) {
|
||||||
this._onClosed.next(this.lastResult);
|
this._onCreated.next(el);
|
||||||
}
|
}
|
||||||
|
|
||||||
created(el: HTMLElement) {
|
onClosedPromise(): Promise<any> {
|
||||||
this._onCreated.next(el);
|
return this.onClosed.pipe(first()).toPromise();
|
||||||
}
|
}
|
||||||
|
|
||||||
onClosedPromise(): Promise<any> {
|
|
||||||
return this.onClosed.pipe(first()).toPromise();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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()
|
@Directive()
|
||||||
export class PasswordGeneratorHistoryComponent implements OnInit {
|
export class PasswordGeneratorHistoryComponent implements OnInit {
|
||||||
history: GeneratedPasswordHistory[] = [];
|
history: GeneratedPasswordHistory[] = [];
|
||||||
|
|
||||||
constructor(protected passwordGenerationService: PasswordGenerationService,
|
constructor(
|
||||||
protected platformUtilsService: PlatformUtilsService, protected i18nService: I18nService,
|
protected passwordGenerationService: PasswordGenerationService,
|
||||||
private win: Window) { }
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
|
protected i18nService: I18nService,
|
||||||
|
private win: Window
|
||||||
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.history = await this.passwordGenerationService.getHistory();
|
this.history = await this.passwordGenerationService.getHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.history = [];
|
this.history = [];
|
||||||
this.passwordGenerationService.clear();
|
this.passwordGenerationService.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
copy(password: string) {
|
copy(password: string) {
|
||||||
const copyOptions = this.win != null ? { window: this.win } : null;
|
const copyOptions = this.win != null ? { window: this.win } : null;
|
||||||
this.platformUtilsService.copyToClipboard(password, copyOptions);
|
this.platformUtilsService.copyToClipboard(password, copyOptions);
|
||||||
this.platformUtilsService.showToast('info', null,
|
this.platformUtilsService.showToast(
|
||||||
this.i18nService.t('valueCopied', this.i18nService.t('password')));
|
"info",
|
||||||
}
|
null,
|
||||||
|
this.i18nService.t("valueCopied", this.i18nService.t("password"))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,101 +1,106 @@
|
||||||
import {
|
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||||
Directive,
|
|
||||||
EventEmitter,
|
|
||||||
Input,
|
|
||||||
OnInit,
|
|
||||||
Output,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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()
|
@Directive()
|
||||||
export class PasswordGeneratorComponent implements OnInit {
|
export class PasswordGeneratorComponent implements OnInit {
|
||||||
@Input() showSelect: boolean = false;
|
@Input() showSelect: boolean = false;
|
||||||
@Output() onSelected = new EventEmitter<string>();
|
@Output() onSelected = new EventEmitter<string>();
|
||||||
|
|
||||||
passTypeOptions: any[];
|
passTypeOptions: any[];
|
||||||
options: any = {};
|
options: any = {};
|
||||||
password: string = '-';
|
password: string = "-";
|
||||||
showOptions = false;
|
showOptions = false;
|
||||||
avoidAmbiguous = false;
|
avoidAmbiguous = false;
|
||||||
enforcedPolicyOptions: PasswordGeneratorPolicyOptions;
|
enforcedPolicyOptions: PasswordGeneratorPolicyOptions;
|
||||||
|
|
||||||
constructor(protected passwordGenerationService: PasswordGenerationService,
|
constructor(
|
||||||
protected platformUtilsService: PlatformUtilsService, protected i18nService: I18nService,
|
protected passwordGenerationService: PasswordGenerationService,
|
||||||
private win: Window) {
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
this.passTypeOptions = [
|
protected i18nService: I18nService,
|
||||||
{ name: i18nService.t('password'), value: 'password' },
|
private win: Window
|
||||||
{ name: i18nService.t('passphrase'), value: 'passphrase' },
|
) {
|
||||||
];
|
this.passTypeOptions = [
|
||||||
}
|
{ name: i18nService.t("password"), value: "password" },
|
||||||
|
{ name: i18nService.t("passphrase"), value: "passphrase" },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
const optionsResponse = await this.passwordGenerationService.getOptions();
|
const optionsResponse = await this.passwordGenerationService.getOptions();
|
||||||
this.options = optionsResponse[0];
|
this.options = optionsResponse[0];
|
||||||
this.enforcedPolicyOptions = optionsResponse[1];
|
this.enforcedPolicyOptions = optionsResponse[1];
|
||||||
this.avoidAmbiguous = !this.options.ambiguous;
|
this.avoidAmbiguous = !this.options.ambiguous;
|
||||||
this.options.type = this.options.type === 'passphrase' ? 'passphrase' : 'password';
|
this.options.type = this.options.type === "passphrase" ? "passphrase" : "password";
|
||||||
this.password = await this.passwordGenerationService.generatePassword(this.options);
|
this.password = await this.passwordGenerationService.generatePassword(this.options);
|
||||||
await this.passwordGenerationService.addHistory(this.password);
|
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() {
|
async regenerate() {
|
||||||
this.saveOptions(false);
|
this.password = await this.passwordGenerationService.generatePassword(this.options);
|
||||||
await this.passwordGenerationService.addHistory(this.password);
|
await this.passwordGenerationService.addHistory(this.password);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sliderInput() {
|
copy() {
|
||||||
this.normalizeOptions();
|
const copyOptions = this.win != null ? { window: this.win } : null;
|
||||||
this.password = await this.passwordGenerationService.generatePassword(this.options);
|
this.platformUtilsService.copyToClipboard(this.password, copyOptions);
|
||||||
}
|
this.platformUtilsService.showToast(
|
||||||
|
"info",
|
||||||
|
null,
|
||||||
|
this.i18nService.t("valueCopied", this.i18nService.t("password"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async saveOptions(regenerate: boolean = true) {
|
select() {
|
||||||
this.normalizeOptions();
|
this.onSelected.emit(this.password);
|
||||||
await this.passwordGenerationService.saveOptions(this.options);
|
}
|
||||||
|
|
||||||
if (regenerate) {
|
toggleOptions() {
|
||||||
await this.regenerate();
|
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.passwordGenerationService.normalizeOptions(this.options, this.enforcedPolicyOptions);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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()
|
@Directive()
|
||||||
export class PasswordHistoryComponent implements OnInit {
|
export class PasswordHistoryComponent implements OnInit {
|
||||||
cipherId: string;
|
cipherId: string;
|
||||||
history: PasswordHistoryView[] = [];
|
history: PasswordHistoryView[] = [];
|
||||||
|
|
||||||
constructor(protected cipherService: CipherService, protected platformUtilsService: PlatformUtilsService,
|
constructor(
|
||||||
protected i18nService: I18nService, private win: Window) { }
|
protected cipherService: CipherService,
|
||||||
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
|
protected i18nService: I18nService,
|
||||||
|
private win: Window
|
||||||
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
await this.init();
|
await this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
copy(password: string) {
|
copy(password: string) {
|
||||||
const copyOptions = this.win != null ? { window: this.win } : null;
|
const copyOptions = this.win != null ? { window: this.win } : null;
|
||||||
this.platformUtilsService.copyToClipboard(password, copyOptions);
|
this.platformUtilsService.copyToClipboard(password, copyOptions);
|
||||||
this.platformUtilsService.showToast('info', null,
|
this.platformUtilsService.showToast(
|
||||||
this.i18nService.t('valueCopied', this.i18nService.t('password')));
|
"info",
|
||||||
}
|
null,
|
||||||
|
this.i18nService.t("valueCopied", this.i18nService.t("password"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
protected async init() {
|
protected async init() {
|
||||||
const cipher = await this.cipherService.get(this.cipherId);
|
const cipher = await this.cipherService.get(this.cipherId);
|
||||||
const decCipher = await cipher.decrypt();
|
const decCipher = await cipher.decrypt();
|
||||||
this.history = decCipher.passwordHistory == null ? [] : decCipher.passwordHistory;
|
this.history = decCipher.passwordHistory == null ? [] : decCipher.passwordHistory;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,36 @@
|
||||||
import { Directive } from '@angular/core';
|
import { Directive } from "@angular/core";
|
||||||
|
|
||||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { ModalRef } from './modal/modal.ref';
|
import { ModalRef } from "./modal/modal.ref";
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class PasswordRepromptComponent {
|
export class PasswordRepromptComponent {
|
||||||
|
showPassword = false;
|
||||||
|
masterPassword = "";
|
||||||
|
|
||||||
showPassword = false;
|
constructor(
|
||||||
masterPassword = '';
|
private modalRef: ModalRef,
|
||||||
|
private cryptoService: CryptoService,
|
||||||
|
private platformUtilsService: PlatformUtilsService,
|
||||||
|
private i18nService: I18nService
|
||||||
|
) {}
|
||||||
|
|
||||||
constructor(private modalRef: ModalRef, private cryptoService: CryptoService, private platformUtilsService: PlatformUtilsService,
|
togglePassword() {
|
||||||
private i18nService: I18nService) {}
|
this.showPassword = !this.showPassword;
|
||||||
|
}
|
||||||
|
|
||||||
togglePassword() {
|
async submit() {
|
||||||
this.showPassword = !this.showPassword;
|
if (!(await this.cryptoService.compareAndUpdateKeyHash(this.masterPassword, null))) {
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"error",
|
||||||
|
this.i18nService.t("errorOccurred"),
|
||||||
|
this.i18nService.t("invalidMasterPassword")
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
this.modalRef.close(true);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class PremiumComponent implements OnInit {
|
export class PremiumComponent implements OnInit {
|
||||||
isPremium: boolean = false;
|
isPremium: boolean = false;
|
||||||
price: number = 10;
|
price: number = 10;
|
||||||
refreshPromise: Promise<any>;
|
refreshPromise: Promise<any>;
|
||||||
|
|
||||||
constructor(protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService,
|
constructor(
|
||||||
protected apiService: ApiService, private logService: LogService,
|
protected i18nService: I18nService,
|
||||||
protected stateService: StateService) { }
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
|
protected apiService: ApiService,
|
||||||
|
private logService: LogService,
|
||||||
|
protected stateService: StateService
|
||||||
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.isPremium = await this.stateService.getCanAccessPremium();
|
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() {
|
async purchase() {
|
||||||
try {
|
const confirmed = await this.platformUtilsService.showDialog(
|
||||||
this.refreshPromise = this.apiService.refreshIdentityToken();
|
this.i18nService.t("premiumPurchaseAlert"),
|
||||||
await this.refreshPromise;
|
this.i18nService.t("premiumPurchase"),
|
||||||
this.platformUtilsService.showToast('success', null, this.i18nService.t('refreshComplete'));
|
this.i18nService.t("yes"),
|
||||||
this.isPremium = await this.stateService.getCanAccessPremium();
|
this.i18nService.t("cancel")
|
||||||
} catch (e) {
|
);
|
||||||
this.logService.error(e);
|
if (confirmed) {
|
||||||
}
|
this.platformUtilsService.launchUri("https://vault.bitwarden.com/#/?premium=purchase");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async purchase() {
|
async manage() {
|
||||||
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('premiumPurchaseAlert'),
|
const confirmed = await this.platformUtilsService.showDialog(
|
||||||
this.i18nService.t('premiumPurchase'), this.i18nService.t('yes'), this.i18nService.t('cancel'));
|
this.i18nService.t("premiumManageAlert"),
|
||||||
if (confirmed) {
|
this.i18nService.t("premiumManage"),
|
||||||
this.platformUtilsService.launchUri('https://vault.bitwarden.com/#/?premium=purchase');
|
this.i18nService.t("yes"),
|
||||||
}
|
this.i18nService.t("cancel")
|
||||||
}
|
);
|
||||||
|
if (confirmed) {
|
||||||
async manage() {
|
this.platformUtilsService.launchUri("https://vault.bitwarden.com/#/?premium=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');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,195 +1,251 @@
|
||||||
import { Directive, OnInit } from '@angular/core';
|
import { Directive, OnInit } from "@angular/core";
|
||||||
import { Router } from '@angular/router';
|
import { Router } from "@angular/router";
|
||||||
|
|
||||||
import { KeysRequest } from 'jslib-common/models/request/keysRequest';
|
import { KeysRequest } from "jslib-common/models/request/keysRequest";
|
||||||
import { ReferenceEventRequest } from 'jslib-common/models/request/referenceEventRequest';
|
import { ReferenceEventRequest } from "jslib-common/models/request/referenceEventRequest";
|
||||||
import { RegisterRequest } from 'jslib-common/models/request/registerRequest';
|
import { RegisterRequest } from "jslib-common/models/request/registerRequest";
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { AuthService } from 'jslib-common/abstractions/auth.service';
|
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.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()
|
@Directive()
|
||||||
export class RegisterComponent extends CaptchaProtectedComponent implements OnInit {
|
export class RegisterComponent extends CaptchaProtectedComponent implements OnInit {
|
||||||
name: string = '';
|
name: string = "";
|
||||||
email: string = '';
|
email: string = "";
|
||||||
masterPassword: string = '';
|
masterPassword: string = "";
|
||||||
confirmMasterPassword: string = '';
|
confirmMasterPassword: string = "";
|
||||||
hint: string = '';
|
hint: string = "";
|
||||||
showPassword: boolean = false;
|
showPassword: boolean = false;
|
||||||
formPromise: Promise<any>;
|
formPromise: Promise<any>;
|
||||||
masterPasswordScore: number;
|
masterPasswordScore: number;
|
||||||
referenceData: ReferenceEventRequest;
|
referenceData: ReferenceEventRequest;
|
||||||
showTerms = true;
|
showTerms = true;
|
||||||
acceptPolicies: boolean = false;
|
acceptPolicies: boolean = false;
|
||||||
|
|
||||||
protected successRoute = 'login';
|
protected successRoute = "login";
|
||||||
private masterPasswordStrengthTimeout: any;
|
private masterPasswordStrengthTimeout: any;
|
||||||
|
|
||||||
constructor(protected authService: AuthService, protected router: Router,
|
constructor(
|
||||||
i18nService: I18nService, protected cryptoService: CryptoService,
|
protected authService: AuthService,
|
||||||
protected apiService: ApiService, protected stateService: StateService,
|
protected router: Router,
|
||||||
platformUtilsService: PlatformUtilsService,
|
i18nService: I18nService,
|
||||||
protected passwordGenerationService: PasswordGenerationService, environmentService: EnvironmentService,
|
protected cryptoService: CryptoService,
|
||||||
protected logService: LogService) {
|
protected apiService: ApiService,
|
||||||
super(environmentService, i18nService, platformUtilsService);
|
protected stateService: StateService,
|
||||||
this.showTerms = !platformUtilsService.isSelfHost();
|
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() {
|
if (this.email == null || this.email === "") {
|
||||||
this.setupCaptcha();
|
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() {
|
const strengthResult = this.passwordGenerationService.passwordStrength(
|
||||||
return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
|
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() {
|
if (this.hint === this.masterPassword) {
|
||||||
switch (this.masterPasswordScore) {
|
this.platformUtilsService.showToast(
|
||||||
case 4:
|
"error",
|
||||||
return 'success';
|
this.i18nService.t("errorOccurred"),
|
||||||
case 3:
|
this.i18nService.t("hintEqualsPassword")
|
||||||
return 'primary';
|
);
|
||||||
case 2:
|
return;
|
||||||
return 'warning';
|
|
||||||
default:
|
|
||||||
return 'danger';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get masterPasswordScoreText() {
|
this.name = this.name === "" ? null : this.name;
|
||||||
switch (this.masterPasswordScore) {
|
this.email = this.email.trim().toLowerCase();
|
||||||
case 4:
|
const kdf = KdfType.PBKDF2_SHA256;
|
||||||
return this.i18nService.t('strong');
|
const useLowerKdf = this.platformUtilsService.isIE();
|
||||||
case 3:
|
const kdfIterations = useLowerKdf ? 10000 : 100000;
|
||||||
return this.i18nService.t('good');
|
const key = await this.cryptoService.makeKey(
|
||||||
case 2:
|
this.masterPassword,
|
||||||
return this.i18nService.t('weak');
|
this.email,
|
||||||
default:
|
kdf,
|
||||||
return this.masterPasswordScore != null ? this.i18nService.t('weak') : null;
|
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() {
|
try {
|
||||||
if (!this.acceptPolicies && this.showTerms) {
|
this.formPromise = this.apiService.postRegister(request);
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
try {
|
||||||
this.i18nService.t('acceptPoliciesError'));
|
await this.formPromise;
|
||||||
return;
|
} catch (e) {
|
||||||
}
|
if (this.handleCaptchaRequired(e)) {
|
||||||
|
return;
|
||||||
if (this.email == null || this.email === '') {
|
} else {
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
throw e;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
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) {
|
togglePassword(confirmField: boolean) {
|
||||||
this.showPassword = !this.showPassword;
|
this.showPassword = !this.showPassword;
|
||||||
document.getElementById(confirmField ? 'masterPasswordRetype' : 'masterPassword').focus();
|
document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePasswordStrength() {
|
updatePasswordStrength() {
|
||||||
if (this.masterPasswordStrengthTimeout != null) {
|
if (this.masterPasswordStrengthTimeout != null) {
|
||||||
clearTimeout(this.masterPasswordStrengthTimeout);
|
clearTimeout(this.masterPasswordStrengthTimeout);
|
||||||
}
|
|
||||||
this.masterPasswordStrengthTimeout = setTimeout(() => {
|
|
||||||
const strengthResult = this.passwordGenerationService.passwordStrength(this.masterPassword,
|
|
||||||
this.getPasswordStrengthUserInput());
|
|
||||||
this.masterPasswordScore = strengthResult == null ? null : strengthResult.score;
|
|
||||||
}, 300);
|
|
||||||
}
|
}
|
||||||
|
this.masterPasswordStrengthTimeout = setTimeout(() => {
|
||||||
|
const strengthResult = this.passwordGenerationService.passwordStrength(
|
||||||
|
this.masterPassword,
|
||||||
|
this.getPasswordStrengthUserInput()
|
||||||
|
);
|
||||||
|
this.masterPasswordScore = strengthResult == null ? null : strengthResult.score;
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
private getPasswordStrengthUserInput() {
|
private getPasswordStrengthUserInput() {
|
||||||
let userInput: string[] = [];
|
let userInput: string[] = [];
|
||||||
const atPosition = this.email.indexOf('@');
|
const atPosition = this.email.indexOf("@");
|
||||||
if (atPosition > -1) {
|
if (atPosition > -1) {
|
||||||
userInput = userInput.concat(this.email.substr(0, atPosition).trim().toLowerCase().split(/[^A-Za-z0-9]/));
|
userInput = userInput.concat(
|
||||||
}
|
this.email
|
||||||
if (this.name != null && this.name !== '') {
|
.substr(0, atPosition)
|
||||||
userInput = userInput.concat(this.name.trim().toLowerCase().split(' '));
|
.trim()
|
||||||
}
|
.toLowerCase()
|
||||||
return userInput;
|
.split(/[^A-Za-z0-9]/)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
if (this.name != null && this.name !== "") {
|
||||||
|
userInput = userInput.concat(this.name.trim().toLowerCase().split(" "));
|
||||||
|
}
|
||||||
|
return userInput;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,74 +1,83 @@
|
||||||
import {
|
import { Directive, OnInit } from "@angular/core";
|
||||||
Directive,
|
import { Router } from "@angular/router";
|
||||||
OnInit,
|
|
||||||
} from '@angular/core';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
|
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
import { SyncService } from 'jslib-common/abstractions/sync.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()
|
@Directive()
|
||||||
export class RemovePasswordComponent implements OnInit {
|
export class RemovePasswordComponent implements OnInit {
|
||||||
|
actionPromise: Promise<any>;
|
||||||
|
continuing: boolean = false;
|
||||||
|
leaving: boolean = false;
|
||||||
|
|
||||||
actionPromise: Promise<any>;
|
loading: boolean = true;
|
||||||
continuing: boolean = false;
|
organization: Organization;
|
||||||
leaving: boolean = false;
|
email: string;
|
||||||
|
|
||||||
loading: boolean = true;
|
constructor(
|
||||||
organization: Organization;
|
private router: Router,
|
||||||
email: string;
|
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,
|
async ngOnInit() {
|
||||||
private apiService: ApiService, private syncService: SyncService,
|
this.organization = await this.keyConnectorService.getManagingOrganization();
|
||||||
private platformUtilsService: PlatformUtilsService, private i18nService: I18nService,
|
this.email = await this.stateService.getEmail();
|
||||||
private keyConnectorService: KeyConnectorService) { }
|
await this.syncService.fullSync(false);
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async convert() {
|
||||||
this.organization = await this.keyConnectorService.getManagingOrganization();
|
this.continuing = true;
|
||||||
this.email = await this.stateService.getEmail();
|
this.actionPromise = this.keyConnectorService.migrateUser();
|
||||||
await this.syncService.fullSync(false);
|
|
||||||
this.loading = false;
|
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() {
|
try {
|
||||||
this.continuing = true;
|
this.leaving = true;
|
||||||
this.actionPromise = this.keyConnectorService.migrateUser();
|
this.actionPromise = this.apiService.postLeaveOrganization(this.organization.id).then(() => {
|
||||||
|
return this.syncService.fullSync(true);
|
||||||
try {
|
});
|
||||||
await this.actionPromise;
|
await this.actionPromise;
|
||||||
this.platformUtilsService.showToast('success', null, this.i18nService.t('removedMasterPassword'));
|
this.platformUtilsService.showToast("success", null, this.i18nService.t("leftOrganization"));
|
||||||
await this.keyConnectorService.removeConvertAccountRequired();
|
await this.keyConnectorService.removeConvertAccountRequired();
|
||||||
this.router.navigate(['']);
|
this.router.navigate([""]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), e.message);
|
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,274 +1,296 @@
|
||||||
import { DatePipe } from '@angular/common';
|
import { DatePipe } from "@angular/common";
|
||||||
import {
|
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||||
Directive,
|
|
||||||
EventEmitter,
|
|
||||||
Input,
|
|
||||||
OnInit,
|
|
||||||
Output
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { PolicyType } from 'jslib-common/enums/policyType';
|
import { PolicyType } from "jslib-common/enums/policyType";
|
||||||
import { SendType } from 'jslib-common/enums/sendType';
|
import { SendType } from "jslib-common/enums/sendType";
|
||||||
|
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||||
import { SendService } from 'jslib-common/abstractions/send.service';
|
import { SendService } from "jslib-common/abstractions/send.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
|
|
||||||
import { SendFileView } from 'jslib-common/models/view/sendFileView';
|
import { SendFileView } from "jslib-common/models/view/sendFileView";
|
||||||
import { SendTextView } from 'jslib-common/models/view/sendTextView';
|
import { SendTextView } from "jslib-common/models/view/sendTextView";
|
||||||
import { SendView } from 'jslib-common/models/view/sendView';
|
import { SendView } from "jslib-common/models/view/sendView";
|
||||||
|
|
||||||
import { EncArrayBuffer } from 'jslib-common/models/domain/encArrayBuffer';
|
import { EncArrayBuffer } from "jslib-common/models/domain/encArrayBuffer";
|
||||||
import { Send } from 'jslib-common/models/domain/send';
|
import { Send } from "jslib-common/models/domain/send";
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class AddEditComponent implements OnInit {
|
export class AddEditComponent implements OnInit {
|
||||||
@Input() sendId: string;
|
@Input() sendId: string;
|
||||||
@Input() type: SendType;
|
@Input() type: SendType;
|
||||||
|
|
||||||
@Output() onSavedSend = new EventEmitter<SendView>();
|
@Output() onSavedSend = new EventEmitter<SendView>();
|
||||||
@Output() onDeletedSend = new EventEmitter<SendView>();
|
@Output() onDeletedSend = new EventEmitter<SendView>();
|
||||||
@Output() onCancelled = new EventEmitter<SendView>();
|
@Output() onCancelled = new EventEmitter<SendView>();
|
||||||
|
|
||||||
copyLink = false;
|
copyLink = false;
|
||||||
disableSend = false;
|
disableSend = false;
|
||||||
disableHideEmail = false;
|
disableHideEmail = false;
|
||||||
send: SendView;
|
send: SendView;
|
||||||
deletionDate: string;
|
deletionDate: string;
|
||||||
expirationDate: string;
|
expirationDate: string;
|
||||||
hasPassword: boolean;
|
hasPassword: boolean;
|
||||||
password: string;
|
password: string;
|
||||||
showPassword = false;
|
showPassword = false;
|
||||||
formPromise: Promise<any>;
|
formPromise: Promise<any>;
|
||||||
deletePromise: Promise<any>;
|
deletePromise: Promise<any>;
|
||||||
sendType = SendType;
|
sendType = SendType;
|
||||||
typeOptions: any[];
|
typeOptions: any[];
|
||||||
canAccessPremium = true;
|
canAccessPremium = true;
|
||||||
emailVerified = true;
|
emailVerified = true;
|
||||||
alertShown = false;
|
alertShown = false;
|
||||||
showOptions = false;
|
showOptions = false;
|
||||||
|
|
||||||
private sendLinkBaseUrl: string;
|
private sendLinkBaseUrl: string;
|
||||||
|
|
||||||
constructor(protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService,
|
constructor(
|
||||||
protected environmentService: EnvironmentService, protected datePipe: DatePipe,
|
protected i18nService: I18nService,
|
||||||
protected sendService: SendService, protected messagingService: MessagingService,
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
protected policyService: PolicyService, private logService: LogService,
|
protected environmentService: EnvironmentService,
|
||||||
protected stateService: StateService) {
|
protected datePipe: DatePipe,
|
||||||
this.typeOptions = [
|
protected sendService: SendService,
|
||||||
{ name: i18nService.t('sendTypeFile'), value: SendType.File },
|
protected messagingService: MessagingService,
|
||||||
{ name: i18nService.t('sendTypeText'), value: SendType.Text },
|
protected policyService: PolicyService,
|
||||||
];
|
private logService: LogService,
|
||||||
this.sendLinkBaseUrl = this.environmentService.getSendUrl();
|
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 == null) {
|
||||||
if (this.send.id != null && this.send.accessId != null) {
|
if (this.editMode) {
|
||||||
return this.sendLinkBaseUrl + this.send.accessId + '/' + this.send.urlB64Key;
|
const send = await this.loadSend();
|
||||||
}
|
this.send = await send.decrypt();
|
||||||
return null;
|
} 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() {
|
this.hasPassword = this.send.password != null && this.send.password.trim() !== "";
|
||||||
return this.platformUtilsService.isSafari();
|
}
|
||||||
|
|
||||||
|
async submit(): Promise<boolean> {
|
||||||
|
if (this.disableSend) {
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"error",
|
||||||
|
this.i18nService.t("errorOccurred"),
|
||||||
|
this.i18nService.t("sendDisabledWarning")
|
||||||
|
);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isDateTimeLocalSupported(): boolean {
|
if (this.send.name == null || this.send.name === "") {
|
||||||
return !(this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari());
|
this.platformUtilsService.showToast(
|
||||||
|
"error",
|
||||||
|
this.i18nService.t("errorOccurred"),
|
||||||
|
this.i18nService.t("nameRequired")
|
||||||
|
);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
let file: File = null;
|
||||||
await this.load();
|
if (this.send.type === SendType.File && !this.editMode) {
|
||||||
}
|
const fileEl = document.getElementById("file") as HTMLInputElement;
|
||||||
|
const files = fileEl.files;
|
||||||
get editMode(): boolean {
|
if (files == null || files.length === 0) {
|
||||||
return this.sendId != null;
|
this.platformUtilsService.showToast(
|
||||||
}
|
"error",
|
||||||
|
this.i18nService.t("errorOccurred"),
|
||||||
get title(): string {
|
this.i18nService.t("selectFile")
|
||||||
return this.i18nService.t(
|
|
||||||
this.editMode ?
|
|
||||||
'editSend' :
|
|
||||||
'createSend'
|
|
||||||
);
|
);
|
||||||
|
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}) {
|
if (this.password != null && this.password.trim() === "") {
|
||||||
this.deletionDate = event.deletionDate;
|
this.password = null;
|
||||||
this.expirationDate = event.expirationDate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
this.formPromise = this.encryptSend(file).then(async (encSend) => {
|
||||||
this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend);
|
const uploadPromise = this.sendService.saveWithServer(encSend);
|
||||||
this.disableHideEmail = await this.policyService.policyAppliesToUser(PolicyType.SendOptions,
|
await uploadPromise;
|
||||||
p => p.data.disableHideEmail);
|
if (this.send.id == null) {
|
||||||
|
this.send.id = encSend[0].id;
|
||||||
this.canAccessPremium = await this.stateService.getCanAccessPremium();
|
}
|
||||||
this.emailVerified = await this.stateService.getEmailVerified();
|
if (this.send.accessId == null) {
|
||||||
if (!this.canAccessPremium || !this.emailVerified) {
|
this.send.accessId = encSend[0].accessId;
|
||||||
this.type = SendType.Text;
|
}
|
||||||
|
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) {
|
async copyLinkToClipboard(link: string): Promise<void | boolean> {
|
||||||
if (this.editMode) {
|
return Promise.resolve(this.platformUtilsService.copyToClipboard(link));
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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> {
|
try {
|
||||||
if (this.disableSend) {
|
this.deletePromise = this.sendService.deleteWithServer(this.send.id);
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
await this.deletePromise;
|
||||||
this.i18nService.t('sendDisabledWarning'));
|
this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedSend"));
|
||||||
return false;
|
await this.load();
|
||||||
}
|
this.onDeletedSend.emit(this.send);
|
||||||
|
return true;
|
||||||
if (this.send.name == null || this.send.name === '') {
|
} catch (e) {
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
this.logService.error(e);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async copyLinkToClipboard(link: string): Promise<void | boolean> {
|
return false;
|
||||||
return Promise.resolve(this.platformUtilsService.copyToClipboard(link));
|
}
|
||||||
|
|
||||||
|
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> {
|
return sendData;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
protected togglePasswordVisible() {
|
||||||
this.deletePromise = this.sendService.deleteWithServer(this.send.id);
|
this.showPassword = !this.showPassword;
|
||||||
await this.deletePromise;
|
document.getElementById("password").focus();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,341 +1,354 @@
|
||||||
import { DatePipe } from '@angular/common';
|
import { DatePipe } from "@angular/common";
|
||||||
import {
|
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||||
Directive,
|
import { FormControl, FormGroup } from "@angular/forms";
|
||||||
EventEmitter,
|
|
||||||
Input,
|
|
||||||
OnInit,
|
|
||||||
Output
|
|
||||||
} from '@angular/core';
|
|
||||||
import { FormControl, FormGroup } from '@angular/forms';
|
|
||||||
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
|
||||||
// Different BrowserPath = different controls.
|
// Different BrowserPath = different controls.
|
||||||
enum BrowserPath {
|
enum BrowserPath {
|
||||||
// Native datetime-locale.
|
// Native datetime-locale.
|
||||||
// We are happy.
|
// We are happy.
|
||||||
Default = 'default',
|
Default = "default",
|
||||||
|
|
||||||
// Native date and time inputs, but no datetime-locale.
|
// Native date and time inputs, but no datetime-locale.
|
||||||
// We use individual date and time inputs and create a datetime programatically on submit.
|
// We use individual date and time inputs and create a datetime programatically on submit.
|
||||||
Firefox = 'firefox',
|
Firefox = "firefox",
|
||||||
|
|
||||||
// No native date, time, or datetime-locale inputs.
|
// No native date, time, or datetime-locale inputs.
|
||||||
// We use a polyfill for dates and a dropdown for times.
|
// We use a polyfill for dates and a dropdown for times.
|
||||||
Safari = 'safari',
|
Safari = "safari",
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DateField {
|
enum DateField {
|
||||||
DeletionDate = 'deletion',
|
DeletionDate = "deletion",
|
||||||
ExpriationDate = 'expiration',
|
ExpriationDate = "expiration",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Value = hours
|
// Value = hours
|
||||||
enum DatePreset {
|
enum DatePreset {
|
||||||
OneHour = 1,
|
OneHour = 1,
|
||||||
OneDay = 24,
|
OneDay = 24,
|
||||||
TwoDays = 48,
|
TwoDays = 48,
|
||||||
ThreeDays = 72,
|
ThreeDays = 72,
|
||||||
SevenDays = 168,
|
SevenDays = 168,
|
||||||
ThirtyDays = 720,
|
ThirtyDays = 720,
|
||||||
Custom = 0,
|
Custom = 0,
|
||||||
Never = null,
|
Never = null,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TimeOption is used for the dropdown implementation of custom times
|
// TimeOption is used for the dropdown implementation of custom times
|
||||||
// twelveHour = displayed time; twentyFourHour = time used in logic
|
// twelveHour = displayed time; twentyFourHour = time used in logic
|
||||||
interface TimeOption {
|
interface TimeOption {
|
||||||
twelveHour: string;
|
twelveHour: string;
|
||||||
twentyFourHour: string;
|
twentyFourHour: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class EffluxDatesComponent implements OnInit {
|
export class EffluxDatesComponent implements OnInit {
|
||||||
@Input() readonly initialDeletionDate: Date;
|
@Input() readonly initialDeletionDate: Date;
|
||||||
@Input() readonly initialExpirationDate: Date;
|
@Input() readonly initialExpirationDate: Date;
|
||||||
@Input() readonly editMode: boolean;
|
@Input() readonly editMode: boolean;
|
||||||
@Input() readonly disabled: boolean;
|
@Input() readonly disabled: boolean;
|
||||||
|
|
||||||
@Output() datesChanged = new EventEmitter<{deletionDate: string, expirationDate: string}>();
|
@Output() datesChanged = new EventEmitter<{ deletionDate: string; expirationDate: string }>();
|
||||||
|
|
||||||
get browserPath(): BrowserPath {
|
get browserPath(): BrowserPath {
|
||||||
if (this.platformUtilsService.isFirefox()) {
|
if (this.platformUtilsService.isFirefox()) {
|
||||||
return BrowserPath.Firefox;
|
return BrowserPath.Firefox;
|
||||||
} else if (this.platformUtilsService.isSafari()) {
|
} else if (this.platformUtilsService.isSafari()) {
|
||||||
return BrowserPath.Safari;
|
return BrowserPath.Safari;
|
||||||
}
|
|
||||||
return BrowserPath.Default;
|
|
||||||
}
|
}
|
||||||
|
return BrowserPath.Default;
|
||||||
|
}
|
||||||
|
|
||||||
datesForm = new FormGroup({
|
datesForm = new FormGroup({
|
||||||
selectedDeletionDatePreset: new FormControl(),
|
selectedDeletionDatePreset: new FormControl(),
|
||||||
selectedExpirationDatePreset: new FormControl(),
|
selectedExpirationDatePreset: new FormControl(),
|
||||||
defaultDeletionDateTime: new FormControl(),
|
defaultDeletionDateTime: new FormControl(),
|
||||||
defaultExpirationDateTime: new FormControl(),
|
defaultExpirationDateTime: new FormControl(),
|
||||||
fallbackDeletionDate: new FormControl(),
|
fallbackDeletionDate: new FormControl(),
|
||||||
fallbackDeletionTime: new FormControl(),
|
fallbackDeletionTime: new FormControl(),
|
||||||
fallbackExpirationDate: new FormControl(),
|
fallbackExpirationDate: new FormControl(),
|
||||||
fallbackExpirationTime: new FormControl(),
|
fallbackExpirationTime: new FormControl(),
|
||||||
});
|
});
|
||||||
|
|
||||||
deletionDatePresets: any[] = [
|
deletionDatePresets: any[] = [
|
||||||
{ name: this.i18nService.t('oneHour'), value: DatePreset.OneHour },
|
{ name: this.i18nService.t("oneHour"), value: DatePreset.OneHour },
|
||||||
{ name: this.i18nService.t('oneDay'), value: DatePreset.OneDay },
|
{ name: this.i18nService.t("oneDay"), value: DatePreset.OneDay },
|
||||||
{ name: this.i18nService.t('days', '2'), value: DatePreset.TwoDays },
|
{ name: this.i18nService.t("days", "2"), value: DatePreset.TwoDays },
|
||||||
{ name: this.i18nService.t('days', '3'), value: DatePreset.ThreeDays },
|
{ name: this.i18nService.t("days", "3"), value: DatePreset.ThreeDays },
|
||||||
{ name: this.i18nService.t('days', '7'), value: DatePreset.SevenDays },
|
{ name: this.i18nService.t("days", "7"), value: DatePreset.SevenDays },
|
||||||
{ name: this.i18nService.t('days', '30'), value: DatePreset.ThirtyDays },
|
{ name: this.i18nService.t("days", "30"), value: DatePreset.ThirtyDays },
|
||||||
{ name: this.i18nService.t('custom'), value: DatePreset.Custom },
|
{ name: this.i18nService.t("custom"), value: DatePreset.Custom },
|
||||||
];
|
];
|
||||||
|
|
||||||
expirationDatePresets: any[] = [
|
expirationDatePresets: any[] = [
|
||||||
{ name: this.i18nService.t('never'), value: DatePreset.Never },
|
{ name: this.i18nService.t("never"), value: DatePreset.Never },
|
||||||
].concat([...this.deletionDatePresets]);
|
].concat([...this.deletionDatePresets]);
|
||||||
|
|
||||||
get selectedDeletionDatePreset(): FormControl {
|
get selectedDeletionDatePreset(): FormControl {
|
||||||
return this.datesForm.get('selectedDeletionDatePreset') as FormControl;
|
return this.datesForm.get("selectedDeletionDatePreset") as FormControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
get selectedExpirationDatePreset(): FormControl {
|
get selectedExpirationDatePreset(): FormControl {
|
||||||
return this.datesForm.get('selectedExpirationDatePreset') as FormControl;
|
return this.datesForm.get("selectedExpirationDatePreset") as FormControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
get defaultDeletionDateTime(): FormControl {
|
get defaultDeletionDateTime(): FormControl {
|
||||||
return this.datesForm.get('defaultDeletionDateTime') as FormControl;
|
return this.datesForm.get("defaultDeletionDateTime") as FormControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
get defaultExpirationDateTime(): FormControl {
|
get defaultExpirationDateTime(): FormControl {
|
||||||
return this.datesForm.get('defaultExpirationDateTime') as FormControl;
|
return this.datesForm.get("defaultExpirationDateTime") as FormControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
get fallbackDeletionDate(): FormControl {
|
get fallbackDeletionDate(): FormControl {
|
||||||
return this.datesForm.get('fallbackDeletionDate') as FormControl;
|
return this.datesForm.get("fallbackDeletionDate") as FormControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
get fallbackDeletionTime(): FormControl {
|
get fallbackDeletionTime(): FormControl {
|
||||||
return this.datesForm.get('fallbackDeletionTime') as FormControl;
|
return this.datesForm.get("fallbackDeletionTime") as FormControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
get fallbackExpirationDate(): FormControl {
|
get fallbackExpirationDate(): FormControl {
|
||||||
return this.datesForm.get('fallbackExpirationDate') as FormControl;
|
return this.datesForm.get("fallbackExpirationDate") as FormControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
get fallbackExpirationTime(): FormControl {
|
get fallbackExpirationTime(): FormControl {
|
||||||
return this.datesForm.get('fallbackExpirationTime') as FormControl;
|
return this.datesForm.get("fallbackExpirationTime") as FormControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should be able to call these at any time and compute a submitable value
|
// Should be able to call these at any time and compute a submitable value
|
||||||
get formattedDeletionDate(): string {
|
get formattedDeletionDate(): string {
|
||||||
switch (this.selectedDeletionDatePreset.value as DatePreset) {
|
switch (this.selectedDeletionDatePreset.value as DatePreset) {
|
||||||
case DatePreset.Never:
|
case DatePreset.Never:
|
||||||
this.selectedDeletionDatePreset.setValue(DatePreset.SevenDays);
|
this.selectedDeletionDatePreset.setValue(DatePreset.SevenDays);
|
||||||
return this.formattedDeletionDate;
|
return this.formattedDeletionDate;
|
||||||
case DatePreset.Custom:
|
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() {
|
|
||||||
switch (this.browserPath) {
|
switch (this.browserPath) {
|
||||||
case BrowserPath.Safari:
|
case BrowserPath.Safari:
|
||||||
case BrowserPath.Firefox:
|
case BrowserPath.Firefox:
|
||||||
this.fallbackExpirationDate.setValue(null);
|
return this.fallbackDeletionDate.value + "T" + this.fallbackDeletionTime.value;
|
||||||
this.fallbackExpirationTime.setValue(null);
|
default:
|
||||||
break;
|
return this.defaultDeletionDateTime.value;
|
||||||
case BrowserPath.Default:
|
|
||||||
this.defaultExpirationDateTime.setValue(null);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
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() {
|
get formattedExpirationDate(): string {
|
||||||
this.datesChanged.emit({
|
switch (this.selectedExpirationDatePreset.value as DatePreset) {
|
||||||
deletionDate: this.formattedDeletionDate,
|
case DatePreset.Never:
|
||||||
expirationDate: this.formattedExpirationDate,
|
return null;
|
||||||
});
|
case DatePreset.Custom:
|
||||||
}
|
switch (this.browserPath) {
|
||||||
|
case BrowserPath.Safari:
|
||||||
protected setInitialFormValues() {
|
case BrowserPath.Firefox:
|
||||||
if (this.editMode) {
|
if (
|
||||||
this.selectedDeletionDatePreset.setValue(DatePreset.Custom);
|
(!this.fallbackExpirationDate.value || !this.fallbackExpirationTime.value) &&
|
||||||
this.selectedExpirationDatePreset.setValue(DatePreset.Custom);
|
this.editMode
|
||||||
switch (this.browserPath) {
|
) {
|
||||||
case BrowserPath.Safari:
|
return null;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
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 {
|
} else {
|
||||||
this.selectedDeletionDatePreset.setValue(DatePreset.SevenDays);
|
const amOption: TimeOption = {
|
||||||
this.selectedExpirationDatePreset.setValue(DatePreset.Never);
|
twelveHour: `${hour}:${minutes} AM`,
|
||||||
|
twentyFourHour: `${hour}:${minutes}`,
|
||||||
|
};
|
||||||
|
ams.push(amOption);
|
||||||
|
|
||||||
switch (this.browserPath) {
|
const pmOption: TimeOption = {
|
||||||
case BrowserPath.Safari:
|
twelveHour: `${hour}:${minutes} PM`,
|
||||||
this.fallbackDeletionDate.setValue(this.nextWeek.toISOString().slice(0, 10));
|
twentyFourHour: `${h + 12}:${minutes}`,
|
||||||
this.fallbackDeletionTime.setValue(this.safariTimePresetOptions(DateField.DeletionDate)[1].twentyFourHour);
|
};
|
||||||
break;
|
pms.push(pmOption);
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected safariTimePresetOptions(field: DateField): TimeOption[] {
|
// bring all the arrays together in the right order
|
||||||
// init individual arrays for major sort groups
|
const validTimes = [...midnight, ...ams, ...noon, ...pms];
|
||||||
const noon: TimeOption[] = [];
|
|
||||||
const midnight: TimeOption[] = [];
|
|
||||||
const ams: TimeOption[] = [];
|
|
||||||
const pms: TimeOption[] = [];
|
|
||||||
|
|
||||||
// determine minute skip (5 min, 10 min, 15 min, etc.)
|
// determine if an unsupported value already exists on the send & add that to the top of the option list
|
||||||
const minuteIncrementer = 15;
|
// example: if the Send was created with a different client
|
||||||
|
if (field === DateField.ExpriationDate && this.initialExpirationDate != null && this.editMode) {
|
||||||
// loop through each hour on a 12 hour system
|
const previousValue: TimeOption = {
|
||||||
for (let h = 1; h <= 12; h++) {
|
twelveHour: this.datePipe.transform(this.initialExpirationDate, "hh:mm a"),
|
||||||
// loop through each minute in the hour using the skip to incriment
|
twentyFourHour: this.datePipe.transform(this.initialExpirationDate, "HH:mm"),
|
||||||
for (let m = 0; m < 60; m += minuteIncrementer) {
|
};
|
||||||
// init the final strings that will be added to the lists
|
return [previousValue, { twelveHour: null, twentyFourHour: null }, ...validTimes];
|
||||||
let hour = h.toString();
|
} else if (
|
||||||
let minutes = m.toString();
|
field === DateField.DeletionDate &&
|
||||||
|
this.initialDeletionDate != null &&
|
||||||
// add prepending 0s to single digit hours/minutes
|
this.editMode
|
||||||
if (h < 10) {
|
) {
|
||||||
hour = '0' + hour;
|
const previousValue: TimeOption = {
|
||||||
}
|
twelveHour: this.datePipe.transform(this.initialDeletionDate, "hh:mm a"),
|
||||||
if (m < 10) {
|
twentyFourHour: this.datePipe.transform(this.initialDeletionDate, "HH:mm"),
|
||||||
minutes = '0' + minutes;
|
};
|
||||||
}
|
return [previousValue, ...validTimes];
|
||||||
|
} else {
|
||||||
// build time strings and push to relevant sort groups
|
return [{ twelveHour: null, twentyFourHour: null }, ...validTimes];
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,201 +1,212 @@
|
||||||
import {
|
import { Directive, NgZone, OnInit } from "@angular/core";
|
||||||
Directive,
|
|
||||||
NgZone,
|
|
||||||
OnInit,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { PolicyType } from 'jslib-common/enums/policyType';
|
import { PolicyType } from "jslib-common/enums/policyType";
|
||||||
import { SendType } from 'jslib-common/enums/sendType';
|
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 { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||||
import { SearchService } from 'jslib-common/abstractions/search.service';
|
import { SearchService } from "jslib-common/abstractions/search.service";
|
||||||
import { SendService } from 'jslib-common/abstractions/send.service';
|
import { SendService } from "jslib-common/abstractions/send.service";
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class SendComponent implements OnInit {
|
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;
|
actionPromise: any;
|
||||||
sendType = SendType;
|
onSuccessfulRemovePassword: () => Promise<any>;
|
||||||
loaded = false;
|
onSuccessfulDelete: () => Promise<any>;
|
||||||
loading = true;
|
onSuccessfulLoad: () => Promise<any>;
|
||||||
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;
|
private searchTimeout: any;
|
||||||
onSuccessfulRemovePassword: () => Promise<any>;
|
|
||||||
onSuccessfulDelete: () => Promise<any>;
|
|
||||||
onSuccessfulLoad: () => Promise<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,
|
async ngOnInit() {
|
||||||
protected platformUtilsService: PlatformUtilsService, protected environmentService: EnvironmentService,
|
this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend);
|
||||||
protected ngZone: NgZone, protected searchService: SearchService,
|
}
|
||||||
protected policyService: PolicyService, private logService: LogService) { }
|
|
||||||
|
|
||||||
async ngOnInit() {
|
async load(filter: (send: SendView) => boolean = null) {
|
||||||
this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend);
|
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) {
|
try {
|
||||||
this.loading = true;
|
this.actionPromise = this.sendService.removePasswordWithServer(s.id);
|
||||||
const sends = await this.sendService.getAllDecrypted();
|
await this.actionPromise;
|
||||||
this.sends = sends;
|
if (this.onSuccessfulRemovePassword != null) {
|
||||||
if (this.onSuccessfulLoad != null) {
|
this.onSuccessfulRemovePassword();
|
||||||
await this.onSuccessfulLoad();
|
} else {
|
||||||
} else {
|
// Default actions
|
||||||
// Default action
|
this.platformUtilsService.showToast("success", null, this.i18nService.t("removedPassword"));
|
||||||
this.selectAll();
|
await this.load();
|
||||||
}
|
}
|
||||||
this.loading = false;
|
} catch (e) {
|
||||||
this.loaded = true;
|
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) {
|
try {
|
||||||
this.loaded = false;
|
this.actionPromise = this.sendService.deleteWithServer(s.id);
|
||||||
this.sends = [];
|
await this.actionPromise;
|
||||||
await this.load(filter);
|
|
||||||
|
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() {
|
copy(s: SendView) {
|
||||||
try {
|
const sendLinkBaseUrl = this.environmentService.getSendUrl();
|
||||||
this.refreshing = true;
|
const link = sendLinkBaseUrl + s.accessId + "/" + s.urlB64Key;
|
||||||
await this.reload(this.filter);
|
this.platformUtilsService.copyToClipboard(link);
|
||||||
} finally {
|
this.platformUtilsService.showToast(
|
||||||
this.refreshing = false;
|
"success",
|
||||||
}
|
null,
|
||||||
}
|
this.i18nService.t("valueCopied", this.i18nService.t("sendLink"))
|
||||||
|
);
|
||||||
async applyFilter(filter: (send: SendView) => boolean = null) {
|
}
|
||||||
this.filter = filter;
|
|
||||||
await this.search(null);
|
searchTextChanged() {
|
||||||
}
|
this.search(200);
|
||||||
|
}
|
||||||
async search(timeout: number = null) {
|
|
||||||
this.searchPending = false;
|
selectAll() {
|
||||||
if (this.searchTimeout != null) {
|
this.clearSelections();
|
||||||
clearTimeout(this.searchTimeout);
|
this.selectedAll = true;
|
||||||
}
|
this.applyFilter(null);
|
||||||
if (timeout == null) {
|
}
|
||||||
this.hasSearched = this.searchService.isSearchable(this.searchText);
|
|
||||||
this.filteredSends = this.sends.filter(s => this.filter == null || this.filter(s));
|
selectType(type: SendType) {
|
||||||
this.applyTextSearch();
|
this.clearSelections();
|
||||||
return;
|
this.selectedType = type;
|
||||||
}
|
this.applyFilter((s) => s.type === type);
|
||||||
this.searchPending = true;
|
}
|
||||||
this.searchTimeout = setTimeout(async () => {
|
|
||||||
this.hasSearched = this.searchService.isSearchable(this.searchText);
|
clearSelections() {
|
||||||
this.filteredSends = this.sends.filter(s => this.filter == null || this.filter(s));
|
this.selectedAll = false;
|
||||||
this.applyTextSearch();
|
this.selectedType = null;
|
||||||
this.searchPending = false;
|
}
|
||||||
}, timeout);
|
|
||||||
}
|
private applyTextSearch() {
|
||||||
|
if (this.searchText != null) {
|
||||||
async removePassword(s: SendView): Promise<boolean> {
|
this.filteredSends = this.searchService.searchSends(this.filteredSends, this.searchText);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,152 +1,184 @@
|
||||||
import { Directive } from '@angular/core';
|
import { Directive } from "@angular/core";
|
||||||
import {
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
ActivatedRoute,
|
|
||||||
Router
|
|
||||||
} from '@angular/router';
|
|
||||||
|
|
||||||
import { first } from 'rxjs/operators';
|
import { first } from "rxjs/operators";
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
import { SyncService } from 'jslib-common/abstractions/sync.service';
|
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||||
|
|
||||||
import { EncString } from 'jslib-common/models/domain/encString';
|
import { EncString } from "jslib-common/models/domain/encString";
|
||||||
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
|
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
|
||||||
|
|
||||||
import { KeysRequest } from 'jslib-common/models/request/keysRequest';
|
import { KeysRequest } from "jslib-common/models/request/keysRequest";
|
||||||
import { OrganizationUserResetPasswordEnrollmentRequest } from 'jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest';
|
import { OrganizationUserResetPasswordEnrollmentRequest } from "jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest";
|
||||||
import { SetPasswordRequest } from 'jslib-common/models/request/setPasswordRequest';
|
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 { HashPurpose } from "jslib-common/enums/hashPurpose";
|
||||||
import { KdfType } from 'jslib-common/enums/kdfType';
|
import { KdfType } from "jslib-common/enums/kdfType";
|
||||||
|
|
||||||
import { Utils } from 'jslib-common/misc/utils';
|
import { Utils } from "jslib-common/misc/utils";
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class SetPasswordComponent extends BaseChangePasswordComponent {
|
export class SetPasswordComponent extends BaseChangePasswordComponent {
|
||||||
syncLoading: boolean = true;
|
syncLoading: boolean = true;
|
||||||
showPassword: boolean = false;
|
showPassword: boolean = false;
|
||||||
hint: string = '';
|
hint: string = "";
|
||||||
identifier: string = null;
|
identifier: string = null;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
resetPasswordAutoEnroll = false;
|
resetPasswordAutoEnroll = false;
|
||||||
|
|
||||||
onSuccessfulChangePassword: () => Promise<any>;
|
onSuccessfulChangePassword: () => Promise<any>;
|
||||||
successRoute = 'vault';
|
successRoute = "vault";
|
||||||
|
|
||||||
constructor(i18nService: I18nService, cryptoService: CryptoService,
|
constructor(
|
||||||
messagingService: MessagingService, passwordGenerationService: PasswordGenerationService,
|
i18nService: I18nService,
|
||||||
platformUtilsService: PlatformUtilsService, policyService: PolicyService,
|
cryptoService: CryptoService,
|
||||||
protected router: Router, private apiService: ApiService,
|
messagingService: MessagingService,
|
||||||
private syncService: SyncService, private route: ActivatedRoute, stateService: StateService) {
|
passwordGenerationService: PasswordGenerationService,
|
||||||
super(i18nService, cryptoService, messagingService, passwordGenerationService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
platformUtilsService, policyService, stateService);
|
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() {
|
super.ngOnInit();
|
||||||
await this.syncService.fullSync(true);
|
}
|
||||||
this.syncLoading = false;
|
|
||||||
|
|
||||||
this.route.queryParams.pipe(first()).subscribe(async qParams => {
|
async setupSubmitActions() {
|
||||||
if (qParams.identifier != null) {
|
this.kdf = KdfType.PBKDF2_SHA256;
|
||||||
this.identifier = qParams.identifier;
|
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
|
await this.formPromise;
|
||||||
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'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
togglePassword(confirmField: boolean) {
|
||||||
this.kdf = KdfType.PBKDF2_SHA256;
|
this.showPassword = !this.showPassword;
|
||||||
const useLowerKdf = this.platformUtilsService.isIE();
|
document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus();
|
||||||
this.kdfIterations = useLowerKdf ? 10000 : 100000;
|
}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async performSubmitActions(masterPasswordHash: string, key: SymmetricCryptoKey,
|
private async onSetPasswordSuccess(
|
||||||
encKey: [SymmetricCryptoKey, EncString]) {
|
key: SymmetricCryptoKey,
|
||||||
const keys = await this.cryptoService.makeKeyPair(encKey[0]);
|
encKey: [SymmetricCryptoKey, EncString],
|
||||||
const request = new SetPasswordRequest(
|
keys: [string, EncString]
|
||||||
masterPasswordHash,
|
) {
|
||||||
encKey[1].encryptedString,
|
await this.stateService.setKdfType(this.kdf);
|
||||||
this.hint,
|
await this.stateService.setKdfIterations(this.kdfIterations);
|
||||||
this.kdf,
|
await this.cryptoService.setKey(key);
|
||||||
this.kdfIterations,
|
await this.cryptoService.setEncKey(encKey[1].encryptedString);
|
||||||
this.identifier,
|
await this.cryptoService.setEncPrivateKey(keys[1].encryptedString);
|
||||||
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 localKeyHash = await this.cryptoService.hashPassword(
|
||||||
const userEncKey = await this.cryptoService.getEncKey();
|
this.masterPassword,
|
||||||
const encryptedKey = await this.cryptoService.rsaEncrypt(userEncKey.key, publicKey.buffer);
|
key,
|
||||||
|
HashPurpose.LocalAuthorization
|
||||||
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
|
);
|
||||||
resetRequest.resetPasswordKey = encryptedKey.encryptedString;
|
await this.cryptoService.setKeyHash(localKeyHash);
|
||||||
|
}
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,53 +1,55 @@
|
||||||
import {
|
import { Directive, OnInit } from "@angular/core";
|
||||||
Directive,
|
|
||||||
OnInit
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
|
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.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()
|
@Directive()
|
||||||
export class SetPinComponent implements OnInit {
|
export class SetPinComponent implements OnInit {
|
||||||
pin = '';
|
pin = "";
|
||||||
showPin = false;
|
showPin = false;
|
||||||
masterPassOnRestart = true;
|
masterPassOnRestart = true;
|
||||||
showMasterPassOnRestart = true;
|
showMasterPassOnRestart = true;
|
||||||
|
|
||||||
constructor(private modalRef: ModalRef, private cryptoService: CryptoService,
|
constructor(
|
||||||
private keyConnectorService: KeyConnectorService, private stateService: StateService) { }
|
private modalRef: ModalRef,
|
||||||
|
private cryptoService: CryptoService,
|
||||||
|
private keyConnectorService: KeyConnectorService,
|
||||||
|
private stateService: StateService
|
||||||
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.showMasterPassOnRestart = this.masterPassOnRestart = !await this.keyConnectorService.getUsesKeyConnector();
|
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() {
|
const kdf = await this.stateService.getKdfType();
|
||||||
this.showPin = !this.showPin;
|
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() {
|
this.modalRef.close(true);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,138 +1,140 @@
|
||||||
|
import { Directive, Input, OnInit } from "@angular/core";
|
||||||
import {
|
import {
|
||||||
Directive,
|
AbstractControl,
|
||||||
Input,
|
ControlValueAccessor,
|
||||||
OnInit,
|
FormBuilder,
|
||||||
} from '@angular/core';
|
ValidationErrors,
|
||||||
import {
|
Validator,
|
||||||
AbstractControl,
|
} from "@angular/forms";
|
||||||
ControlValueAccessor,
|
|
||||||
FormBuilder,
|
|
||||||
ValidationErrors,
|
|
||||||
Validator
|
|
||||||
} from '@angular/forms';
|
|
||||||
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||||
|
|
||||||
import { PolicyType } from 'jslib-common/enums/policyType';
|
import { PolicyType } from "jslib-common/enums/policyType";
|
||||||
import { Policy } from 'jslib-common/models/domain/policy';
|
import { Policy } from "jslib-common/models/domain/policy";
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class VaultTimeoutInputComponent implements ControlValueAccessor, Validator, OnInit {
|
export class VaultTimeoutInputComponent implements ControlValueAccessor, Validator, OnInit {
|
||||||
|
get showCustom() {
|
||||||
|
return this.form.get("vaultTimeout").value === VaultTimeoutInputComponent.CUSTOM_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
get showCustom() {
|
static CUSTOM_VALUE = -100;
|
||||||
return this.form.get('vaultTimeout').value === VaultTimeoutInputComponent.CUSTOM_VALUE;
|
|
||||||
|
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;
|
this.form.valueChanges.subscribe(async (value) => {
|
||||||
|
this.onChange(this.getVaultTimeout(value));
|
||||||
form = this.fb.group({
|
|
||||||
vaultTimeout: [null],
|
|
||||||
custom: this.fb.group({
|
|
||||||
hours: [null],
|
|
||||||
minutes: [null],
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@Input() vaultTimeouts: { name: string; value: number; }[];
|
// Assign the previous value to the custom fields
|
||||||
vaultTimeoutPolicy: Policy;
|
this.form.get("vaultTimeout").valueChanges.subscribe((value) => {
|
||||||
vaultTimeoutPolicyHours: number;
|
if (value !== VaultTimeoutInputComponent.CUSTOM_VALUE) {
|
||||||
vaultTimeoutPolicyMinutes: number;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
private onChange: (vaultTimeout: number) => void;
|
const current = Math.max(this.form.value.vaultTimeout, 0);
|
||||||
private validatorChange: () => void;
|
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() {
|
return value.custom.hours * 60 + value.custom.minutes;
|
||||||
if (await this.policyService.policyAppliesToUser(PolicyType.MaximumVaultTimeout)) {
|
}
|
||||||
const vaultTimeoutPolicy = await this.policyService.getAll(PolicyType.MaximumVaultTimeout);
|
|
||||||
|
|
||||||
this.vaultTimeoutPolicy = vaultTimeoutPolicy[0];
|
writeValue(value: number): void {
|
||||||
this.vaultTimeoutPolicyHours = Math.floor(this.vaultTimeoutPolicy.data.minutes / 60);
|
if (value == null) {
|
||||||
this.vaultTimeoutPolicyMinutes = this.vaultTimeoutPolicy.data.minutes % 60;
|
return;
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges() {
|
if (this.vaultTimeouts.every((p) => p.value !== value)) {
|
||||||
this.vaultTimeouts.push({ name: this.i18nService.t('custom'), value: VaultTimeoutInputComponent.CUSTOM_VALUE });
|
this.form.setValue({
|
||||||
|
vaultTimeout: VaultTimeoutInputComponent.CUSTOM_VALUE,
|
||||||
|
custom: {
|
||||||
|
hours: Math.floor(value / 60),
|
||||||
|
minutes: value % 60,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
getVaultTimeout(value: any) {
|
this.form.patchValue({
|
||||||
if (value.vaultTimeout !== VaultTimeoutInputComponent.CUSTOM_VALUE) {
|
vaultTimeout: value,
|
||||||
return value.vaultTimeout;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
return null;
|
||||||
if (value == null) {
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.vaultTimeouts.every(p => p.value !== value)) {
|
registerOnValidatorChange(fn: () => void): void {
|
||||||
this.form.setValue({
|
this.validatorChange = fn;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,108 +1,119 @@
|
||||||
import {
|
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||||
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 { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||||
import { CollectionService } from 'jslib-common/abstractions/collection.service';
|
import { CollectionService } from "jslib-common/abstractions/collection.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
|
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
|
||||||
import { Organization } from 'jslib-common/models/domain/organization';
|
import { Organization } from "jslib-common/models/domain/organization";
|
||||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||||
import { CollectionView } from 'jslib-common/models/view/collectionView';
|
import { CollectionView } from "jslib-common/models/view/collectionView";
|
||||||
|
|
||||||
import { Utils } from 'jslib-common/misc/utils';
|
import { Utils } from "jslib-common/misc/utils";
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class ShareComponent implements OnInit {
|
export class ShareComponent implements OnInit {
|
||||||
@Input() cipherId: string;
|
@Input() cipherId: string;
|
||||||
@Input() organizationId: string;
|
@Input() organizationId: string;
|
||||||
@Output() onSharedCipher = new EventEmitter();
|
@Output() onSharedCipher = new EventEmitter();
|
||||||
|
|
||||||
formPromise: Promise<any>;
|
formPromise: Promise<any>;
|
||||||
cipher: CipherView;
|
cipher: CipherView;
|
||||||
collections: CollectionView[] = [];
|
collections: CollectionView[] = [];
|
||||||
organizations: Organization[] = [];
|
organizations: Organization[] = [];
|
||||||
|
|
||||||
protected writeableCollections: CollectionView[] = [];
|
protected writeableCollections: CollectionView[] = [];
|
||||||
|
|
||||||
constructor(protected collectionService: CollectionService, protected platformUtilsService: PlatformUtilsService,
|
constructor(
|
||||||
protected i18nService: I18nService, protected cipherService: CipherService,
|
protected collectionService: CollectionService,
|
||||||
private logService: LogService, protected organizationService: OrganizationService) { }
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
|
protected i18nService: I18nService,
|
||||||
|
protected cipherService: CipherService,
|
||||||
|
private logService: LogService,
|
||||||
|
protected organizationService: OrganizationService
|
||||||
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
await this.load();
|
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 cipherDomain = await this.cipherService.get(this.cipherId);
|
||||||
const allCollections = await this.collectionService.getAllDecrypted();
|
const cipherView = await cipherDomain.decrypt();
|
||||||
this.writeableCollections = allCollections.map(c => c).filter(c => !c.readOnly);
|
const orgName =
|
||||||
const orgs = await this.organizationService.getAll();
|
this.organizations.find((o) => o.id === this.organizationId)?.name ??
|
||||||
this.organizations = orgs.sort(Utils.getSortFunction(this.i18nService, 'name'))
|
this.i18nService.t("organization");
|
||||||
.filter(o => o.enabled && o.status === OrganizationUserStatusType.Confirmed);
|
|
||||||
|
|
||||||
const cipherDomain = await this.cipherService.get(this.cipherId);
|
try {
|
||||||
this.cipher = await cipherDomain.decrypt();
|
this.formPromise = this.cipherService
|
||||||
if (this.organizationId == null && this.organizations.length > 0) {
|
.shareWithServer(cipherView, this.organizationId, selectedCollectionIds)
|
||||||
this.organizationId = this.organizations[0].id;
|
.then(async () => {
|
||||||
}
|
this.onSharedCipher.emit();
|
||||||
this.filterCollections();
|
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() {
|
get canSave() {
|
||||||
this.writeableCollections.forEach(c => (c as any).checked = false);
|
if (this.collections != null) {
|
||||||
if (this.organizationId == null || this.writeableCollections.length === 0) {
|
for (let i = 0; i < this.collections.length; i++) {
|
||||||
this.collections = [];
|
if ((this.collections[i] as any).checked) {
|
||||||
} else {
|
return true;
|
||||||
this.collections = this.writeableCollections.filter(c => c.organizationId === this.organizationId);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,211 +1,254 @@
|
||||||
import { Directive } from '@angular/core';
|
import { Directive } from "@angular/core";
|
||||||
import {
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
ActivatedRoute,
|
|
||||||
Router,
|
|
||||||
} from '@angular/router';
|
|
||||||
|
|
||||||
import { first } from 'rxjs/operators';
|
import { first } from "rxjs/operators";
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { AuthService } from 'jslib-common/abstractions/auth.service';
|
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||||
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
|
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.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()
|
@Directive()
|
||||||
export class SsoComponent {
|
export class SsoComponent {
|
||||||
identifier: string;
|
identifier: string;
|
||||||
loggingIn = false;
|
loggingIn = false;
|
||||||
|
|
||||||
formPromise: Promise<AuthResult>;
|
formPromise: Promise<AuthResult>;
|
||||||
initiateSsoFormPromise: Promise<any>;
|
initiateSsoFormPromise: Promise<any>;
|
||||||
onSuccessfulLogin: () => Promise<any>;
|
onSuccessfulLogin: () => Promise<any>;
|
||||||
onSuccessfulLoginNavigate: () => Promise<any>;
|
onSuccessfulLoginNavigate: () => Promise<any>;
|
||||||
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>;
|
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>;
|
||||||
onSuccessfulLoginChangePasswordNavigate: () => Promise<any>;
|
onSuccessfulLoginChangePasswordNavigate: () => Promise<any>;
|
||||||
onSuccessfulLoginForceResetNavigate: () => Promise<any>;
|
onSuccessfulLoginForceResetNavigate: () => Promise<any>;
|
||||||
|
|
||||||
protected twoFactorRoute = '2fa';
|
protected twoFactorRoute = "2fa";
|
||||||
protected successRoute = 'lock';
|
protected successRoute = "lock";
|
||||||
protected changePasswordRoute = 'set-password';
|
protected changePasswordRoute = "set-password";
|
||||||
protected forcePasswordResetRoute = 'update-temp-password';
|
protected forcePasswordResetRoute = "update-temp-password";
|
||||||
protected clientId: string;
|
protected clientId: string;
|
||||||
protected redirectUri: string;
|
protected redirectUri: string;
|
||||||
protected state: string;
|
protected state: string;
|
||||||
protected codeChallenge: string;
|
protected codeChallenge: string;
|
||||||
|
|
||||||
constructor(protected authService: AuthService, protected router: Router,
|
constructor(
|
||||||
protected i18nService: I18nService, protected route: ActivatedRoute,
|
protected authService: AuthService,
|
||||||
protected stateService: StateService, protected platformUtilsService: PlatformUtilsService,
|
protected router: Router,
|
||||||
protected apiService: ApiService, protected cryptoFunctionService: CryptoFunctionService,
|
protected i18nService: I18nService,
|
||||||
protected environmentService: EnvironmentService, protected passwordGenerationService: PasswordGenerationService,
|
protected route: ActivatedRoute,
|
||||||
protected logService: LogService) { }
|
protected stateService: StateService,
|
||||||
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
|
protected apiService: ApiService,
|
||||||
|
protected cryptoFunctionService: CryptoFunctionService,
|
||||||
|
protected environmentService: EnvironmentService,
|
||||||
|
protected passwordGenerationService: PasswordGenerationService,
|
||||||
|
protected logService: LogService
|
||||||
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.route.queryParams.pipe(first()).subscribe(async qParams => {
|
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||||
if (qParams.code != null && qParams.state != null) {
|
if (qParams.code != null && qParams.state != null) {
|
||||||
const codeVerifier = await this.stateService.getSsoCodeVerifier();
|
const codeVerifier = await this.stateService.getSsoCodeVerifier();
|
||||||
const state = await this.stateService.getSsoState();
|
const state = await this.stateService.getSsoState();
|
||||||
await this.stateService.setSsoCodeVerifier(null);
|
await this.stateService.setSsoCodeVerifier(null);
|
||||||
await this.stateService.setSsoState(null);
|
await this.stateService.setSsoState(null);
|
||||||
if (qParams.code != null && codeVerifier != null && state != null && this.checkState(state, qParams.state)) {
|
if (
|
||||||
await this.logIn(qParams.code, codeVerifier, this.getOrgIdentifierFromState(qParams.state));
|
qParams.code != null &&
|
||||||
}
|
codeVerifier != null &&
|
||||||
} else if (qParams.clientId != null && qParams.redirectUri != null && qParams.state != null &&
|
state != null &&
|
||||||
qParams.codeChallenge != null) {
|
this.checkState(state, qParams.state)
|
||||||
this.redirectUri = qParams.redirectUri;
|
) {
|
||||||
this.state = qParams.state;
|
await this.logIn(
|
||||||
this.codeChallenge = qParams.codeChallenge;
|
qParams.code,
|
||||||
this.clientId = qParams.clientId;
|
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) {
|
if (state == null) {
|
||||||
this.initiateSsoFormPromise = this.preValidate();
|
state = await this.passwordGenerationService.generatePassword(passwordOptions);
|
||||||
if (await this.initiateSsoFormPromise) {
|
if (returnUri) {
|
||||||
const authorizeUrl = await this.buildAuthorizeUrl(returnUri, includeUserIdentifier);
|
state += `_returnUri='${returnUri}'`;
|
||||||
this.platformUtilsService.launchUri(authorizeUrl, { sameWindow: true });
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async preValidate(): Promise<boolean> {
|
// Add Organization Identifier to state
|
||||||
if (this.identifier == null || this.identifier === '') {
|
state += `_identifier=${this.identifier}`;
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('ssoValidationFailed'),
|
|
||||||
this.i18nService.t('ssoIdentifierRequired'));
|
// Save state (regardless of new or existing)
|
||||||
return false;
|
await this.stateService.setSsoState(state);
|
||||||
}
|
|
||||||
return await this.apiService.preValidateSso(this.identifier);
|
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> {
|
return authorizeUrl;
|
||||||
let codeChallenge = this.codeChallenge;
|
}
|
||||||
let state = this.state;
|
|
||||||
|
|
||||||
const passwordOptions: any = {
|
private async logIn(code: string, codeVerifier: string, orgIdFromState: string) {
|
||||||
type: 'password',
|
this.loggingIn = true;
|
||||||
length: 64,
|
try {
|
||||||
uppercase: true,
|
this.formPromise = this.authService.logInSso(
|
||||||
lowercase: true,
|
code,
|
||||||
numbers: true,
|
codeVerifier,
|
||||||
special: false,
|
this.redirectUri,
|
||||||
};
|
orgIdFromState
|
||||||
|
);
|
||||||
if (codeChallenge == null) {
|
const response = await this.formPromise;
|
||||||
const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
|
if (response.twoFactor) {
|
||||||
const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, 'sha256');
|
if (this.onSuccessfulLoginTwoFactorNavigate != null) {
|
||||||
codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
|
this.onSuccessfulLoginTwoFactorNavigate();
|
||||||
await this.stateService.setSsoCodeVerifier(codeVerifier);
|
} else {
|
||||||
|
this.router.navigate([this.twoFactorRoute], {
|
||||||
|
queryParams: {
|
||||||
|
identifier: orgIdFromState,
|
||||||
|
sso: "true",
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
} else if (response.resetMasterPassword) {
|
||||||
if (state == null) {
|
if (this.onSuccessfulLoginChangePasswordNavigate != null) {
|
||||||
state = await this.passwordGenerationService.generatePassword(passwordOptions);
|
this.onSuccessfulLoginChangePasswordNavigate();
|
||||||
if (returnUri) {
|
} else {
|
||||||
state += `_returnUri='${returnUri}'`;
|
this.router.navigate([this.changePasswordRoute], {
|
||||||
}
|
queryParams: {
|
||||||
|
identifier: orgIdFromState,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
} else if (response.forcePasswordReset) {
|
||||||
// Add Organization Identifier to state
|
if (this.onSuccessfulLoginForceResetNavigate != null) {
|
||||||
state += `_identifier=${this.identifier}`;
|
this.onSuccessfulLoginForceResetNavigate();
|
||||||
|
} else {
|
||||||
// Save state (regardless of new or existing)
|
this.router.navigate([this.forcePasswordResetRoute]);
|
||||||
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 {
|
||||||
|
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) {
|
const stateSplit = state.split("_identifier=");
|
||||||
this.loggingIn = true;
|
return stateSplit.length > 1 ? stateSplit[1] : null;
|
||||||
try {
|
}
|
||||||
this.formPromise = this.authService.logInSso(code, codeVerifier, this.redirectUri, orgIdFromState);
|
|
||||||
const response = await this.formPromise;
|
private checkState(state: string, checkState: string): boolean {
|
||||||
if (response.twoFactor) {
|
if (state === null || state === undefined) {
|
||||||
if (this.onSuccessfulLoginTwoFactorNavigate != null) {
|
return false;
|
||||||
this.onSuccessfulLoginTwoFactorNavigate();
|
}
|
||||||
} else {
|
if (checkState === null || checkState === undefined) {
|
||||||
this.router.navigate([this.twoFactorRoute], {
|
return false;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getOrgIdentifierFromState(state: string): string {
|
const stateSplit = state.split("_identifier=");
|
||||||
if (state === null || state === undefined) {
|
const checkStateSplit = checkState.split("_identifier=");
|
||||||
return null;
|
return stateSplit[0] === checkStateSplit[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
import {
|
||||||
animate,
|
DefaultNoComponentGlobalConfig,
|
||||||
state,
|
GlobalConfig,
|
||||||
style,
|
Toast as BaseToast,
|
||||||
transition,
|
ToastPackage,
|
||||||
trigger
|
ToastrService,
|
||||||
} from '@angular/animations';
|
TOAST_CONFIG,
|
||||||
import { CommonModule } from '@angular/common';
|
} from "ngx-toastr";
|
||||||
import { Component, ModuleWithProviders, NgModule } from '@angular/core';
|
|
||||||
import { DefaultNoComponentGlobalConfig, GlobalConfig, Toast as BaseToast, ToastPackage, ToastrService, TOAST_CONFIG } from 'ngx-toastr';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: '[toast-component2]',
|
selector: "[toast-component2]",
|
||||||
template: `
|
template: `
|
||||||
<button *ngIf="options.closeButton" (click)="remove()" type="button" class="toast-close-button" aria-label="Close">
|
<button
|
||||||
<span aria-hidden="true">×</span>
|
*ngIf="options.closeButton"
|
||||||
|
(click)="remove()"
|
||||||
|
type="button"
|
||||||
|
class="toast-close-button"
|
||||||
|
aria-label="Close"
|
||||||
|
>
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<i></i>
|
<i></i>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div *ngIf="title" [class]="options.titleClass" [attr.aria-label]="title">
|
<div *ngIf="title" [class]="options.titleClass" [attr.aria-label]="title">
|
||||||
{{ title }} <ng-container *ngIf="duplicatesCount">[{{ duplicatesCount + 1 }}]</ng-container>
|
{{ title }} <ng-container *ngIf="duplicatesCount">[{{ duplicatesCount + 1 }}]</ng-container>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="message && options.enableHtml" role="alertdialog" aria-live="polite"
|
<div
|
||||||
[class]="options.messageClass" [innerHTML]="message">
|
*ngIf="message && options.enableHtml"
|
||||||
</div>
|
role="alertdialog"
|
||||||
<div *ngIf="message && !options.enableHtml" role="alertdialog" aria-live="polite"
|
aria-live="polite"
|
||||||
[class]="options.messageClass" [attr.aria-label]="message">
|
[class]="options.messageClass"
|
||||||
{{ message }}
|
[innerHTML]="message"
|
||||||
</div>
|
></div>
|
||||||
|
<div
|
||||||
|
*ngIf="message && !options.enableHtml"
|
||||||
|
role="alertdialog"
|
||||||
|
aria-live="polite"
|
||||||
|
[class]="options.messageClass"
|
||||||
|
[attr.aria-label]="message"
|
||||||
|
>
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="options.progressBar">
|
<div *ngIf="options.progressBar">
|
||||||
<div class="toast-progress" [style.width]="width + '%'"></div>
|
<div class="toast-progress" [style.width]="width + '%'"></div>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
animations: [
|
animations: [
|
||||||
trigger('flyInOut', [
|
trigger("flyInOut", [
|
||||||
state('inactive', style({ opacity: 0 })),
|
state("inactive", style({ opacity: 0 })),
|
||||||
state('active', style({ opacity: 1 })),
|
state("active", style({ opacity: 1 })),
|
||||||
state('removed', style({ opacity: 0 })),
|
state("removed", style({ opacity: 0 })),
|
||||||
transition(
|
transition("inactive => active", animate("{{ easeTime }}ms {{ easing }}")),
|
||||||
'inactive => active',
|
transition("active => removed", animate("{{ easeTime }}ms {{ easing }}")),
|
||||||
animate('{{ easeTime }}ms {{ easing }}')
|
]),
|
||||||
),
|
],
|
||||||
transition(
|
preserveWhitespaces: false,
|
||||||
'active => removed',
|
|
||||||
animate('{{ easeTime }}ms {{ easing }}')
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
preserveWhitespaces: false,
|
|
||||||
})
|
})
|
||||||
export class BitwardenToast extends BaseToast {
|
export class BitwardenToast extends BaseToast {
|
||||||
constructor(protected toastrService: ToastrService, public toastPackage: ToastPackage) {
|
constructor(protected toastrService: ToastrService, public toastPackage: ToastPackage) {
|
||||||
super(toastrService, toastPackage);
|
super(toastrService, toastPackage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BitwardenToastGlobalConfig: GlobalConfig = {
|
export const BitwardenToastGlobalConfig: GlobalConfig = {
|
||||||
...DefaultNoComponentGlobalConfig,
|
...DefaultNoComponentGlobalConfig,
|
||||||
toastComponent: BitwardenToast,
|
toastComponent: BitwardenToast,
|
||||||
};
|
};
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule],
|
imports: [CommonModule],
|
||||||
declarations: [BitwardenToast],
|
declarations: [BitwardenToast],
|
||||||
exports: [BitwardenToast],
|
exports: [BitwardenToast],
|
||||||
})
|
})
|
||||||
export class BitwardenToastModule {
|
export class BitwardenToastModule {
|
||||||
static forRoot(config: Partial<GlobalConfig> = {}): ModuleWithProviders<BitwardenToastModule> {
|
static forRoot(config: Partial<GlobalConfig> = {}): ModuleWithProviders<BitwardenToastModule> {
|
||||||
return {
|
return {
|
||||||
ngModule: BitwardenToastModule,
|
ngModule: BitwardenToastModule,
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
provide: TOAST_CONFIG,
|
provide: TOAST_CONFIG,
|
||||||
useValue: {
|
useValue: {
|
||||||
default: BitwardenToastGlobalConfig,
|
default: BitwardenToastGlobalConfig,
|
||||||
config: config,
|
config: config,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,37 @@
|
||||||
import {
|
import { Directive, EventEmitter, OnInit, Output } from "@angular/core";
|
||||||
Directive,
|
import { Router } from "@angular/router";
|
||||||
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 { AuthService } from "jslib-common/abstractions/auth.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class TwoFactorOptionsComponent implements OnInit {
|
export class TwoFactorOptionsComponent implements OnInit {
|
||||||
@Output() onProviderSelected = new EventEmitter<TwoFactorProviderType>();
|
@Output() onProviderSelected = new EventEmitter<TwoFactorProviderType>();
|
||||||
@Output() onRecoverSelected = new EventEmitter();
|
@Output() onRecoverSelected = new EventEmitter();
|
||||||
|
|
||||||
providers: any[] = [];
|
providers: any[] = [];
|
||||||
|
|
||||||
constructor(protected authService: AuthService, protected router: Router,
|
constructor(
|
||||||
protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService,
|
protected authService: AuthService,
|
||||||
protected win: Window) { }
|
protected router: Router,
|
||||||
|
protected i18nService: I18nService,
|
||||||
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
|
protected win: Window
|
||||||
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.providers = this.authService.getSupportedTwoFactorProviders(this.win);
|
this.providers = this.authService.getSupportedTwoFactorProviders(this.win);
|
||||||
}
|
}
|
||||||
|
|
||||||
choose(p: any) {
|
choose(p: any) {
|
||||||
this.onProviderSelected.emit(p.type);
|
this.onProviderSelected.emit(p.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
recover() {
|
recover() {
|
||||||
this.platformUtilsService.launchUri('https://help.bitwarden.com/article/lost-two-step-device/');
|
this.platformUtilsService.launchUri("https://help.bitwarden.com/article/lost-two-step-device/");
|
||||||
this.onRecoverSelected.emit();
|
this.onRecoverSelected.emit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,251 +1,280 @@
|
||||||
import { Directive, OnDestroy, OnInit } from '@angular/core';
|
import { Directive, OnDestroy, OnInit } from "@angular/core";
|
||||||
|
|
||||||
import {
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
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 { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { AuthService } from 'jslib-common/abstractions/auth.service';
|
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.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 * as DuoWebSDK from "duo_web_sdk";
|
||||||
import { WebAuthnIFrame } from 'jslib-common/misc/webauthn_iframe';
|
import { WebAuthnIFrame } from "jslib-common/misc/webauthn_iframe";
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class TwoFactorComponent implements OnInit, OnDestroy {
|
export class TwoFactorComponent implements OnInit, OnDestroy {
|
||||||
token: string = '';
|
token: string = "";
|
||||||
remember: boolean = false;
|
remember: boolean = false;
|
||||||
webAuthnReady: boolean = false;
|
webAuthnReady: boolean = false;
|
||||||
webAuthnNewTab: boolean = false;
|
webAuthnNewTab: boolean = false;
|
||||||
providers = TwoFactorProviders;
|
providers = TwoFactorProviders;
|
||||||
providerType = TwoFactorProviderType;
|
providerType = TwoFactorProviderType;
|
||||||
selectedProviderType: TwoFactorProviderType = TwoFactorProviderType.Authenticator;
|
selectedProviderType: TwoFactorProviderType = TwoFactorProviderType.Authenticator;
|
||||||
webAuthnSupported: boolean = false;
|
webAuthnSupported: boolean = false;
|
||||||
webAuthn: WebAuthnIFrame = null;
|
webAuthn: WebAuthnIFrame = null;
|
||||||
title: string = '';
|
title: string = "";
|
||||||
twoFactorEmail: string = null;
|
twoFactorEmail: string = null;
|
||||||
formPromise: Promise<any>;
|
formPromise: Promise<any>;
|
||||||
emailPromise: Promise<any>;
|
emailPromise: Promise<any>;
|
||||||
identifier: string = null;
|
identifier: string = null;
|
||||||
onSuccessfulLogin: () => Promise<any>;
|
onSuccessfulLogin: () => Promise<any>;
|
||||||
onSuccessfulLoginNavigate: () => Promise<any>;
|
onSuccessfulLoginNavigate: () => Promise<any>;
|
||||||
|
|
||||||
get webAuthnAllow(): string {
|
get webAuthnAllow(): string {
|
||||||
return `publickey-credentials-get ${this.environmentService.getWebVaultUrl()}`;
|
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';
|
this.route.queryParams.pipe(first()).subscribe((qParams) => {
|
||||||
protected successRoute = 'vault';
|
if (qParams.identifier != null) {
|
||||||
|
this.identifier = qParams.identifier;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
constructor(protected authService: AuthService, protected router: Router,
|
if (this.needsLock) {
|
||||||
protected i18nService: I18nService, protected apiService: ApiService,
|
this.successRoute = "lock";
|
||||||
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.win != null && this.webAuthnSupported) {
|
||||||
if (!this.authing || this.authService.twoFactorProvidersData == null) {
|
const webVaultUrl = this.environmentService.getWebVaultUrl();
|
||||||
this.router.navigate([this.loginRoute]);
|
this.webAuthn = new WebAuthnIFrame(
|
||||||
return;
|
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.selectedProviderType = this.authService.getDefaultTwoFactorProvider(
|
||||||
this.cleanupWebAuthn();
|
this.webAuthnSupported
|
||||||
this.webAuthn = null;
|
);
|
||||||
|
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() {
|
this.cleanupWebAuthn();
|
||||||
if (this.selectedProviderType == null) {
|
this.title = (TwoFactorProviders as any)[this.selectedProviderType].name;
|
||||||
this.title = this.i18nService.t('loginUnavailable');
|
const providerData = this.authService.twoFactorProvidersData.get(this.selectedProviderType);
|
||||||
return;
|
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();
|
async submit() {
|
||||||
this.title = (TwoFactorProviders as any)[this.selectedProviderType].name;
|
if (this.token == null || this.token === "") {
|
||||||
const providerData = this.authService.twoFactorProvidersData.get(this.selectedProviderType);
|
this.platformUtilsService.showToast(
|
||||||
switch (this.selectedProviderType) {
|
"error",
|
||||||
case TwoFactorProviderType.WebAuthn:
|
this.i18nService.t("errorOccurred"),
|
||||||
if (!this.webAuthnNewTab) {
|
this.i18nService.t("verificationCodeRequired")
|
||||||
setTimeout(() => {
|
);
|
||||||
this.authWebAuthn();
|
return;
|
||||||
}, 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.selectedProviderType === TwoFactorProviderType.WebAuthn) {
|
||||||
if (this.token == null || this.token === '') {
|
if (this.webAuthn != null) {
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
this.webAuthn.stop();
|
||||||
this.i18nService.t('verificationCodeRequired'));
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else if (
|
||||||
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn) {
|
this.selectedProviderType === TwoFactorProviderType.Email ||
|
||||||
if (this.webAuthn != null) {
|
this.selectedProviderType === TwoFactorProviderType.Authenticator
|
||||||
this.webAuthn.stop();
|
) {
|
||||||
} else {
|
this.token = this.token.replace(" ", "").trim();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async doSubmit() {
|
try {
|
||||||
this.formPromise = this.authService.logInTwoFactor(this.selectedProviderType, this.token, this.remember);
|
await this.doSubmit();
|
||||||
const response: AuthResult = await this.formPromise;
|
} catch {
|
||||||
const disableFavicon = await this.stateService.getDisableFavicon();
|
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && this.webAuthn != null) {
|
||||||
await this.stateService.setDisableFavicon(!!disableFavicon);
|
this.webAuthn.start();
|
||||||
if (this.onSuccessfulLogin != null) {
|
}
|
||||||
this.onSuccessfulLogin();
|
}
|
||||||
}
|
}
|
||||||
if (response.resetMasterPassword) {
|
|
||||||
this.successRoute = 'set-password';
|
async doSubmit() {
|
||||||
}
|
this.formPromise = this.authService.logInTwoFactor(
|
||||||
if (response.forcePasswordReset) {
|
this.selectedProviderType,
|
||||||
this.successRoute = 'update-temp-password';
|
this.token,
|
||||||
}
|
this.remember
|
||||||
if (this.onSuccessfulLoginNavigate != null) {
|
);
|
||||||
this.onSuccessfulLoginNavigate();
|
const response: AuthResult = await this.formPromise;
|
||||||
} else {
|
const disableFavicon = await this.stateService.getDisableFavicon();
|
||||||
this.router.navigate([this.successRoute], {
|
await this.stateService.setDisableFavicon(!!disableFavicon);
|
||||||
queryParams: {
|
if (this.onSuccessfulLogin != null) {
|
||||||
identifier: this.identifier,
|
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.emailPromise != null) {
|
||||||
if (this.selectedProviderType !== TwoFactorProviderType.Email) {
|
return;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
authWebAuthn() {
|
try {
|
||||||
const providerData = this.authService.twoFactorProvidersData.get(this.selectedProviderType);
|
const request = new TwoFactorEmailRequest();
|
||||||
|
request.email = this.authService.email;
|
||||||
if (!this.webAuthnSupported || this.webAuthn == null) {
|
request.masterPasswordHash = this.authService.masterPasswordHash;
|
||||||
return;
|
this.emailPromise = this.apiService.postTwoFactorEmail(request);
|
||||||
}
|
await this.emailPromise;
|
||||||
|
if (doToast) {
|
||||||
this.webAuthn.init(providerData);
|
this.platformUtilsService.showToast(
|
||||||
|
"success",
|
||||||
|
null,
|
||||||
|
this.i18nService.t("verificationCodeEmailSent", this.twoFactorEmail)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.logService.error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private cleanupWebAuthn() {
|
this.emailPromise = null;
|
||||||
if (this.webAuthn != null) {
|
}
|
||||||
this.webAuthn.stop();
|
|
||||||
this.webAuthn.cleanup();
|
authWebAuthn() {
|
||||||
}
|
const providerData = this.authService.twoFactorProvidersData.get(this.selectedProviderType);
|
||||||
|
|
||||||
|
if (!this.webAuthnSupported || this.webAuthn == null) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
get authing(): boolean {
|
this.webAuthn.init(providerData);
|
||||||
return this.authService.authingWithPassword() || this.authService.authingWithSso() || this.authService.authingWithApiKey();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
get needsLock(): boolean {
|
private cleanupWebAuthn() {
|
||||||
return this.authService.authingWithSso() || this.authService.authingWithApiKey();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,109 +1,134 @@
|
||||||
import { Directive } from '@angular/core';
|
import { Directive } from "@angular/core";
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
import { SyncService } from 'jslib-common/abstractions/sync.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 { EncString } from "jslib-common/models/domain/encString";
|
||||||
import { MasterPasswordPolicyOptions } from 'jslib-common/models/domain/masterPasswordPolicyOptions';
|
import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPasswordPolicyOptions";
|
||||||
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
|
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()
|
@Directive()
|
||||||
export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
|
export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
|
||||||
hint: string;
|
hint: string;
|
||||||
key: string;
|
key: string;
|
||||||
enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
||||||
showPassword: boolean = false;
|
showPassword: boolean = false;
|
||||||
|
|
||||||
onSuccessfulChangePassword: () => Promise<any>;
|
onSuccessfulChangePassword: () => Promise<any>;
|
||||||
|
|
||||||
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
|
constructor(
|
||||||
passwordGenerationService: PasswordGenerationService, policyService: PolicyService,
|
i18nService: I18nService,
|
||||||
cryptoService: CryptoService, messagingService: MessagingService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
private apiService: ApiService, stateService: StateService,
|
passwordGenerationService: PasswordGenerationService,
|
||||||
private syncService: SyncService, private logService: LogService) {
|
policyService: PolicyService,
|
||||||
super(i18nService, cryptoService, messagingService, passwordGenerationService,
|
cryptoService: CryptoService,
|
||||||
platformUtilsService, policyService, stateService);
|
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() {
|
if (!(await this.setupSubmitActions())) {
|
||||||
await this.syncService.fullSync(true);
|
return;
|
||||||
super.ngOnInit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
togglePassword(confirmField: boolean) {
|
try {
|
||||||
this.showPassword = !this.showPassword;
|
// Create new key and hash new password
|
||||||
document.getElementById(confirmField ? 'masterPasswordRetype' : 'masterPassword').focus();
|
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> {
|
async performSubmitActions(
|
||||||
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
|
masterPasswordHash: string,
|
||||||
this.email = await this.stateService.getEmail();
|
key: SymmetricCryptoKey,
|
||||||
this.kdf = await this.stateService.getKdfType();
|
encKey: [SymmetricCryptoKey, EncString]
|
||||||
this.kdfIterations = await this.stateService.getKdfIterations();
|
) {
|
||||||
return true;
|
try {
|
||||||
}
|
// Create request
|
||||||
|
const request = new UpdateTempPasswordRequest();
|
||||||
async submit() {
|
request.key = encKey[1].encryptedString;
|
||||||
// Validation
|
request.newMasterPasswordHash = masterPasswordHash;
|
||||||
if (!await this.strongPassword()) {
|
request.masterPasswordHint = this.hint;
|
||||||
return;
|
|
||||||
}
|
// Update user's password
|
||||||
|
this.formPromise = this.apiService.putUpdateTempPassword(request);
|
||||||
if (!await this.setupSubmitActions()) {
|
await this.formPromise;
|
||||||
return;
|
this.platformUtilsService.showToast(
|
||||||
}
|
"success",
|
||||||
|
null,
|
||||||
try {
|
this.i18nService.t("updatedMasterPassword")
|
||||||
// Create new key and hash new password
|
);
|
||||||
const newKey = await this.cryptoService.makeKey(this.masterPassword, this.email.trim().toLowerCase(),
|
|
||||||
this.kdf, this.kdfIterations);
|
if (this.onSuccessfulChangePassword != null) {
|
||||||
const newPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, newKey);
|
this.onSuccessfulChangePassword();
|
||||||
|
} else {
|
||||||
// Grab user's current enc key
|
this.messagingService.send("logout");
|
||||||
const userEncKey = await this.cryptoService.getEncKey();
|
}
|
||||||
|
} catch (e) {
|
||||||
// Create new encKey for the User
|
this.logService.error(e);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,46 @@
|
||||||
<ng-container *ngIf="!usesKeyConnector">
|
<ng-container *ngIf="!usesKeyConnector">
|
||||||
<label for="masterPassword">{{'masterPass' | i18n}}</label>
|
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
||||||
<input id="masterPassword" type="password" name="MasterPasswordHash" class="form-control"
|
<input
|
||||||
[formControl]="secret" required appAutofocus appInputVerbatim>
|
id="masterPassword"
|
||||||
<small class="form-text text-muted">{{'confirmIdentity' | i18n}}</small>
|
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>
|
||||||
<ng-container *ngIf="usesKeyConnector">
|
<ng-container *ngIf="usesKeyConnector">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="d-block">{{'sendVerificationCode' | i18n}}</label>
|
<label class="d-block">{{ "sendVerificationCode" | i18n }}</label>
|
||||||
<button type="button" class="btn btn-outline-secondary" (click)="requestOTP()" [disabled]="disableRequestOTP">
|
<button
|
||||||
{{'sendCode' | i18n}}
|
type="button"
|
||||||
</button>
|
class="btn btn-outline-secondary"
|
||||||
<span class="ml-2 text-success" role="alert" @sent *ngIf="sentCode">
|
(click)="requestOTP()"
|
||||||
<i class="fa fa-check-circle-o" aria-hidden="true"></i>
|
[disabled]="disableRequestOTP"
|
||||||
{{'codeSent' | i18n}}
|
>
|
||||||
</span>
|
{{ "sendCode" | i18n }}
|
||||||
</div>
|
</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">
|
<div class="form-group">
|
||||||
<label for="verificationCode">{{'verificationCode' | i18n}}</label>
|
<label for="verificationCode">{{ "verificationCode" | i18n }}</label>
|
||||||
<input id="verificationCode" type="input" name="verificationCode" class="form-control"
|
<input
|
||||||
[formControl]="secret" required appAutofocus appInputVerbatim>
|
id="verificationCode"
|
||||||
<small class="form-text text-muted">{{'confirmIdentity' | i18n}}</small>
|
type="input"
|
||||||
</div>
|
name="verificationCode"
|
||||||
|
class="form-control"
|
||||||
|
[formControl]="secret"
|
||||||
|
required
|
||||||
|
appAutofocus
|
||||||
|
appInputVerbatim
|
||||||
|
/>
|
||||||
|
<small class="form-text text-muted">{{ "confirmIdentity" | i18n }}</small>
|
||||||
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
|
@ -1,105 +1,92 @@
|
||||||
import {
|
import { animate, style, transition, trigger } from "@angular/animations";
|
||||||
animate,
|
import { Component, OnInit } from "@angular/core";
|
||||||
style,
|
import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from "@angular/forms";
|
||||||
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 { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
||||||
import { UserVerificationService } from 'jslib-common/abstractions/userVerification.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({
|
@Component({
|
||||||
selector: 'app-verify-master-password',
|
selector: "app-verify-master-password",
|
||||||
templateUrl: 'verify-master-password.component.html',
|
templateUrl: "verify-master-password.component.html",
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
provide: NG_VALUE_ACCESSOR,
|
provide: NG_VALUE_ACCESSOR,
|
||||||
multi: true,
|
multi: true,
|
||||||
useExisting: VerifyMasterPasswordComponent,
|
useExisting: VerifyMasterPasswordComponent,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
animations: [
|
animations: [
|
||||||
trigger('sent', [
|
trigger("sent", [
|
||||||
transition(':enter', [
|
transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]),
|
||||||
style({ opacity: 0 }),
|
]),
|
||||||
animate('100ms', style({ opacity: 1 })),
|
],
|
||||||
]),
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class VerifyMasterPasswordComponent implements ControlValueAccessor, OnInit {
|
export class VerifyMasterPasswordComponent implements ControlValueAccessor, OnInit {
|
||||||
usesKeyConnector: boolean = false;
|
usesKeyConnector: boolean = false;
|
||||||
disableRequestOTP: boolean = false;
|
disableRequestOTP: boolean = false;
|
||||||
sentCode: 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,
|
constructor(
|
||||||
private userVerificationService: UserVerificationService) { }
|
private keyConnectorService: KeyConnectorService,
|
||||||
|
private userVerificationService: UserVerificationService
|
||||||
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
|
this.usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
|
||||||
this.processChanges(this.secret.value);
|
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() {
|
this.onChange({
|
||||||
if (this.usesKeyConnector) {
|
type: this.usesKeyConnector ? VerificationType.OTP : VerificationType.MasterPassword,
|
||||||
this.disableRequestOTP = true;
|
secret: secret,
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,32 @@
|
||||||
import {
|
import { Directive, Input } from "@angular/core";
|
||||||
Directive,
|
|
||||||
Input,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { EventType } from 'jslib-common/enums/eventType';
|
import { EventType } from "jslib-common/enums/eventType";
|
||||||
import { FieldType } from 'jslib-common/enums/fieldType';
|
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 { CipherView } from "jslib-common/models/view/cipherView";
|
||||||
import { FieldView } from 'jslib-common/models/view/fieldView';
|
import { FieldView } from "jslib-common/models/view/fieldView";
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class ViewCustomFieldsComponent {
|
export class ViewCustomFieldsComponent {
|
||||||
@Input() cipher: CipherView;
|
@Input() cipher: CipherView;
|
||||||
@Input() promptPassword: () => Promise<boolean>;
|
@Input() promptPassword: () => Promise<boolean>;
|
||||||
@Input() copy: (value: string, typeI18nKey: string, aType: string) => void;
|
@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) {
|
async toggleFieldValue(field: FieldView) {
|
||||||
if (!await this.promptPassword()) {
|
if (!(await this.promptPassword())) {
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
const f = (field as any);
|
|
||||||
f.showValue = !f.showValue;
|
|
||||||
if (f.showValue) {
|
|
||||||
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipher.id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const f = field as any;
|
||||||
|
f.showValue = !f.showValue;
|
||||||
|
if (f.showValue) {
|
||||||
|
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipher.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,394 +1,447 @@
|
||||||
import {
|
import {
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
Directive,
|
Directive,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
Input,
|
Input,
|
||||||
NgZone,
|
NgZone,
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
OnInit,
|
OnInit,
|
||||||
Output,
|
Output,
|
||||||
} from '@angular/core';
|
} from "@angular/core";
|
||||||
|
|
||||||
import { CipherRepromptType } from 'jslib-common/enums/cipherRepromptType';
|
import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType";
|
||||||
import { CipherType } from 'jslib-common/enums/cipherType';
|
import { CipherType } from "jslib-common/enums/cipherType";
|
||||||
import { EventType } from 'jslib-common/enums/eventType';
|
import { EventType } from "jslib-common/enums/eventType";
|
||||||
import { FieldType } from 'jslib-common/enums/fieldType';
|
import { FieldType } from "jslib-common/enums/fieldType";
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { AuditService } from 'jslib-common/abstractions/audit.service';
|
import { AuditService } from "jslib-common/abstractions/audit.service";
|
||||||
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
|
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { EventService } from 'jslib-common/abstractions/event.service';
|
import { EventService } from "jslib-common/abstractions/event.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
|
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
import { TokenService } from 'jslib-common/abstractions/token.service';
|
import { TokenService } from "jslib-common/abstractions/token.service";
|
||||||
import { TotpService } from 'jslib-common/abstractions/totp.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 { AttachmentView } from "jslib-common/models/view/attachmentView";
|
||||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||||
import { LoginUriView } from 'jslib-common/models/view/loginUriView';
|
import { LoginUriView } from "jslib-common/models/view/loginUriView";
|
||||||
|
|
||||||
const BroadcasterSubscriptionId = 'ViewComponent';
|
const BroadcasterSubscriptionId = "ViewComponent";
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class ViewComponent implements OnDestroy, OnInit {
|
export class ViewComponent implements OnDestroy, OnInit {
|
||||||
@Input() cipherId: string;
|
@Input() cipherId: string;
|
||||||
@Output() onEditCipher = new EventEmitter<CipherView>();
|
@Output() onEditCipher = new EventEmitter<CipherView>();
|
||||||
@Output() onCloneCipher = new EventEmitter<CipherView>();
|
@Output() onCloneCipher = new EventEmitter<CipherView>();
|
||||||
@Output() onShareCipher = new EventEmitter<CipherView>();
|
@Output() onShareCipher = new EventEmitter<CipherView>();
|
||||||
@Output() onDeletedCipher = new EventEmitter<CipherView>();
|
@Output() onDeletedCipher = new EventEmitter<CipherView>();
|
||||||
@Output() onRestoredCipher = new EventEmitter<CipherView>();
|
@Output() onRestoredCipher = new EventEmitter<CipherView>();
|
||||||
|
|
||||||
cipher: CipherView;
|
cipher: CipherView;
|
||||||
showPassword: boolean;
|
showPassword: boolean;
|
||||||
showCardNumber: boolean;
|
showCardNumber: boolean;
|
||||||
showCardCode: boolean;
|
showCardCode: boolean;
|
||||||
canAccessPremium: boolean;
|
canAccessPremium: boolean;
|
||||||
totpCode: string;
|
totpCode: string;
|
||||||
totpCodeFormatted: string;
|
totpCodeFormatted: string;
|
||||||
totpDash: number;
|
totpDash: number;
|
||||||
totpSec: number;
|
totpSec: number;
|
||||||
totpLow: boolean;
|
totpLow: boolean;
|
||||||
fieldType = FieldType;
|
fieldType = FieldType;
|
||||||
checkPasswordPromise: Promise<number>;
|
checkPasswordPromise: Promise<number>;
|
||||||
|
|
||||||
private totpInterval: any;
|
private totpInterval: any;
|
||||||
private previousCipherId: string;
|
private previousCipherId: string;
|
||||||
private passwordReprompted: boolean = false;
|
private passwordReprompted: boolean = false;
|
||||||
|
|
||||||
constructor(protected cipherService: CipherService, protected totpService: TotpService,
|
constructor(
|
||||||
protected tokenService: TokenService, protected i18nService: I18nService,
|
protected cipherService: CipherService,
|
||||||
protected cryptoService: CryptoService, protected platformUtilsService: PlatformUtilsService,
|
protected totpService: TotpService,
|
||||||
protected auditService: AuditService, protected win: Window,
|
protected tokenService: TokenService,
|
||||||
protected broadcasterService: BroadcasterService, protected ngZone: NgZone,
|
protected i18nService: I18nService,
|
||||||
protected changeDetectorRef: ChangeDetectorRef, protected eventService: EventService,
|
protected cryptoService: CryptoService,
|
||||||
protected apiService: ApiService, protected passwordRepromptService: PasswordRepromptService,
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
private logService: LogService, protected stateService: StateService) { }
|
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() {
|
ngOnInit() {
|
||||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
||||||
this.ngZone.run(async () => {
|
this.ngZone.run(async () => {
|
||||||
switch (message.command) {
|
switch (message.command) {
|
||||||
case 'syncCompleted':
|
case "syncCompleted":
|
||||||
if (message.successfully) {
|
if (message.successfully) {
|
||||||
await this.load();
|
await this.load();
|
||||||
this.changeDetectorRef.detectChanges();
|
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;
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
a.downloading = true;
|
ngOnDestroy() {
|
||||||
const response = await fetch(new Request(url, { cache: 'no-store' }));
|
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||||
if (response.status !== 200) {
|
this.cleanUp();
|
||||||
this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred'));
|
}
|
||||||
a.downloading = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
async load() {
|
||||||
const buf = await response.arrayBuffer();
|
this.cleanUp();
|
||||||
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;
|
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() {
|
if (this.previousCipherId !== this.cipherId) {
|
||||||
return this.cipher.isDeleted ? this.cipherService.deleteWithServer(this.cipher.id)
|
this.eventService.collect(EventType.Cipher_ClientViewed, this.cipherId);
|
||||||
: this.cipherService.softDeleteWithServer(this.cipher.id);
|
}
|
||||||
|
this.previousCipherId = this.cipherId;
|
||||||
|
}
|
||||||
|
|
||||||
|
async edit() {
|
||||||
|
if (await this.promptPassword()) {
|
||||||
|
this.onEditCipher.emit(this.cipher);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected restoreCipher() {
|
return false;
|
||||||
return this.cipherService.restoreWithServer(this.cipher.id);
|
}
|
||||||
|
|
||||||
|
async clone() {
|
||||||
|
if (await this.promptPassword()) {
|
||||||
|
this.onCloneCipher.emit(this.cipher);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async promptPassword() {
|
return false;
|
||||||
if (this.cipher.reprompt === CipherRepromptType.None || this.passwordReprompted) {
|
}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.passwordReprompted = await this.passwordRepromptService.showPasswordPrompt();
|
async share() {
|
||||||
|
if (await this.promptPassword()) {
|
||||||
|
this.onShareCipher.emit(this.cipher);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private cleanUp() {
|
return false;
|
||||||
this.totpCode = null;
|
}
|
||||||
this.cipher = null;
|
|
||||||
this.showPassword = false;
|
async delete(): Promise<boolean> {
|
||||||
this.showCardNumber = false;
|
if (!(await this.promptPassword())) {
|
||||||
this.showCardCode = false;
|
return;
|
||||||
this.passwordReprompted = false;
|
|
||||||
if (this.totpInterval) {
|
|
||||||
clearInterval(this.totpInterval);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async totpUpdateCode() {
|
const confirmed = await this.platformUtilsService.showDialog(
|
||||||
if (this.cipher == null || this.cipher.type !== CipherType.Login || this.cipher.login.totp == null) {
|
this.i18nService.t(
|
||||||
if (this.totpInterval) {
|
this.cipher.isDeleted ? "permanentlyDeleteItemConfirmation" : "deleteItemConfirmation"
|
||||||
clearInterval(this.totpInterval);
|
),
|
||||||
}
|
this.i18nService.t("deleteItem"),
|
||||||
return;
|
this.i18nService.t("yes"),
|
||||||
}
|
this.i18nService.t("no"),
|
||||||
|
"warning"
|
||||||
this.totpCode = await this.totpService.getCode(this.cipher.login.totp);
|
);
|
||||||
if (this.totpCode != null) {
|
if (!confirmed) {
|
||||||
if (this.totpCode.length > 4) {
|
return false;
|
||||||
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) {
|
try {
|
||||||
const epoch = Math.round(new Date().getTime() / 1000.0);
|
await this.deleteCipher();
|
||||||
const mod = epoch % intervalSeconds;
|
this.platformUtilsService.showToast(
|
||||||
|
"success",
|
||||||
this.totpSec = intervalSeconds - mod;
|
null,
|
||||||
this.totpDash = +(Math.round((((78.6 / intervalSeconds) * mod) + 'e+2') as any) + 'e-2');
|
this.i18nService.t(this.cipher.isDeleted ? "permanentlyDeletedItem" : "deletedItem")
|
||||||
this.totpLow = this.totpSec <= 7;
|
);
|
||||||
if (mod === 0) {
|
this.onDeletedCipher.emit(this.cipher);
|
||||||
await this.totpUpdateCode();
|
} 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,23 @@
|
||||||
import {
|
import { Directive, ElementRef, Input, Renderer2 } from "@angular/core";
|
||||||
Directive,
|
|
||||||
ElementRef,
|
|
||||||
Input,
|
|
||||||
Renderer2,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[appA11yTitle]',
|
selector: "[appA11yTitle]",
|
||||||
})
|
})
|
||||||
export class A11yTitleDirective {
|
export class A11yTitleDirective {
|
||||||
@Input() set appA11yTitle(title: string) {
|
@Input() set appA11yTitle(title: string) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
private title: string;
|
||||||
|
|
||||||
|
constructor(private el: ElementRef, private renderer: Renderer2) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
if (!this.el.nativeElement.hasAttribute("title")) {
|
||||||
|
this.renderer.setAttribute(this.el.nativeElement, "title", this.title);
|
||||||
}
|
}
|
||||||
|
if (!this.el.nativeElement.hasAttribute("aria-label")) {
|
||||||
private title: string;
|
this.renderer.setAttribute(this.el.nativeElement, "aria-label", this.title);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +1,46 @@
|
||||||
import {
|
import { Directive, ElementRef, Input, OnChanges } from "@angular/core";
|
||||||
Directive,
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
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({
|
@Directive({
|
||||||
selector: '[appApiAction]',
|
selector: "[appApiAction]",
|
||||||
})
|
})
|
||||||
export class ApiActionDirective implements OnChanges {
|
export class ApiActionDirective implements OnChanges {
|
||||||
@Input() appApiAction: Promise<any>;
|
@Input() appApiAction: Promise<any>;
|
||||||
|
|
||||||
constructor(private el: ElementRef, private validationService: ValidationService,
|
constructor(
|
||||||
private logService: LogService) { }
|
private el: ElementRef,
|
||||||
|
private validationService: ValidationService,
|
||||||
|
private logService: LogService
|
||||||
|
) {}
|
||||||
|
|
||||||
ngOnChanges(changes: any) {
|
ngOnChanges(changes: any) {
|
||||||
if (this.appApiAction == null || this.appApiAction.then == null) {
|
if (this.appApiAction == null || this.appApiAction.then == null) {
|
||||||
return;
|
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +1,28 @@
|
||||||
import {
|
import { Directive, ElementRef, Input, NgZone } from "@angular/core";
|
||||||
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({
|
@Directive({
|
||||||
selector: '[appAutofocus]',
|
selector: "[appAutofocus]",
|
||||||
})
|
})
|
||||||
export class AutofocusDirective {
|
export class AutofocusDirective {
|
||||||
@Input() set appAutofocus(condition: boolean | string) {
|
@Input() set appAutofocus(condition: boolean | string) {
|
||||||
this.autofocus = condition === '' || condition === true;
|
this.autofocus = condition === "" || condition === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private autofocus: boolean;
|
private autofocus: boolean;
|
||||||
|
|
||||||
constructor(private el: ElementRef, private ngZone: NgZone) { }
|
constructor(private el: ElementRef, private ngZone: NgZone) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
if (!Utils.isMobileBrowser && this.autofocus) {
|
if (!Utils.isMobileBrowser && this.autofocus) {
|
||||||
if (this.ngZone.isStable) {
|
if (this.ngZone.isStable) {
|
||||||
this.el.nativeElement.focus();
|
this.el.nativeElement.focus();
|
||||||
} else {
|
} else {
|
||||||
this.ngZone.onStable.pipe(take(1)).subscribe(() => this.el.nativeElement.focus());
|
this.ngZone.onStable.pipe(take(1)).subscribe(() => this.el.nativeElement.focus());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,12 @@
|
||||||
import {
|
import { Directive, ElementRef, HostListener } from "@angular/core";
|
||||||
Directive,
|
|
||||||
ElementRef,
|
|
||||||
HostListener,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[appBlurClick]',
|
selector: "[appBlurClick]",
|
||||||
})
|
})
|
||||||
export class BlurClickDirective {
|
export class BlurClickDirective {
|
||||||
constructor(private el: ElementRef) {
|
constructor(private el: ElementRef) {}
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('click') onClick() {
|
@HostListener("click") onClick() {
|
||||||
this.el.nativeElement.blur();
|
this.el.nativeElement.blur();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,51 +1,59 @@
|
||||||
import {
|
import { Directive, ElementRef, HostListener, OnInit } from "@angular/core";
|
||||||
Directive,
|
|
||||||
ElementRef,
|
|
||||||
HostListener,
|
|
||||||
OnInit,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[appBoxRow]',
|
selector: "[appBoxRow]",
|
||||||
})
|
})
|
||||||
export class BoxRowDirective implements OnInit {
|
export class BoxRowDirective implements OnInit {
|
||||||
el: HTMLElement = null;
|
el: HTMLElement = null;
|
||||||
formEls: Element[];
|
formEls: Element[];
|
||||||
|
|
||||||
constructor(private elRef: ElementRef) {
|
constructor(private elRef: ElementRef) {
|
||||||
this.el = elRef.nativeElement;
|
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 {
|
if (this.formEls.length > 0) {
|
||||||
this.formEls = Array.from(this.el.querySelectorAll('input:not([type="hidden"]), select, textarea'));
|
const formEl = this.formEls[0] as HTMLElement;
|
||||||
this.formEls.forEach(formEl => {
|
if (formEl.tagName.toLowerCase() === "input") {
|
||||||
formEl.addEventListener('focus', (event: Event) => {
|
const inputEl = formEl as HTMLInputElement;
|
||||||
this.el.classList.add('active');
|
if (inputEl.type != null && inputEl.type.toLowerCase() === "checkbox") {
|
||||||
}, false);
|
inputEl.click();
|
||||||
|
return;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
formEl.focus();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,62 +1,76 @@
|
||||||
import {
|
import {
|
||||||
CdkFixedSizeVirtualScroll,
|
CdkFixedSizeVirtualScroll,
|
||||||
FixedSizeVirtualScrollStrategy,
|
FixedSizeVirtualScrollStrategy,
|
||||||
VIRTUAL_SCROLL_STRATEGY,
|
VIRTUAL_SCROLL_STRATEGY,
|
||||||
} from '@angular/cdk/scrolling';
|
} from "@angular/cdk/scrolling";
|
||||||
import {
|
import { Directive, forwardRef } from "@angular/core";
|
||||||
Directive,
|
|
||||||
forwardRef,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
// Custom virtual scroll strategy for cdk-virtual-scroll
|
// Custom virtual scroll strategy for cdk-virtual-scroll
|
||||||
// Uses a sample list item to set the itemSize for FixedSizeVirtualScrollStrategy
|
// 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.
|
// The use case is the same as FixedSizeVirtualScrollStrategy, but it avoids locking in pixel sizes in the template.
|
||||||
export class CipherListVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy {
|
export class CipherListVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy {
|
||||||
private checkItemSizeCallback: any;
|
private checkItemSizeCallback: any;
|
||||||
private timeout: any;
|
private timeout: any;
|
||||||
|
|
||||||
constructor(itemSize: number, minBufferPx: number, maxBufferPx: number, checkItemSizeCallback: any) {
|
constructor(
|
||||||
super(itemSize, minBufferPx, maxBufferPx);
|
itemSize: number,
|
||||||
this.checkItemSizeCallback = checkItemSizeCallback;
|
minBufferPx: number,
|
||||||
|
maxBufferPx: number,
|
||||||
|
checkItemSizeCallback: any
|
||||||
|
) {
|
||||||
|
super(itemSize, minBufferPx, maxBufferPx);
|
||||||
|
this.checkItemSizeCallback = checkItemSizeCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
onContentRendered() {
|
||||||
|
if (this.timeout != null) {
|
||||||
|
clearTimeout(this.timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
onContentRendered() {
|
this.timeout = setTimeout(this.checkItemSizeCallback, 500);
|
||||||
if (this.timeout != null) {
|
}
|
||||||
clearTimeout(this.timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.timeout = setTimeout(this.checkItemSizeCallback, 500);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function _cipherListVirtualScrollStrategyFactory(cipherListDir: CipherListVirtualScroll) {
|
export function _cipherListVirtualScrollStrategyFactory(cipherListDir: CipherListVirtualScroll) {
|
||||||
return cipherListDir._scrollStrategy;
|
return cipherListDir._scrollStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: 'cdk-virtual-scroll-viewport[itemSize]',
|
selector: "cdk-virtual-scroll-viewport[itemSize]",
|
||||||
providers: [{
|
providers: [
|
||||||
provide: VIRTUAL_SCROLL_STRATEGY,
|
{
|
||||||
useFactory: _cipherListVirtualScrollStrategyFactory,
|
provide: VIRTUAL_SCROLL_STRATEGY,
|
||||||
deps: [forwardRef(() => CipherListVirtualScroll)],
|
useFactory: _cipherListVirtualScrollStrategyFactory,
|
||||||
}],
|
deps: [forwardRef(() => CipherListVirtualScroll)],
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class CipherListVirtualScroll extends CdkFixedSizeVirtualScroll {
|
export class CipherListVirtualScroll extends CdkFixedSizeVirtualScroll {
|
||||||
_scrollStrategy: CipherListVirtualScrollStrategy;
|
_scrollStrategy: CipherListVirtualScrollStrategy;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this._scrollStrategy = new CipherListVirtualScrollStrategy(this.itemSize, this.minBufferPx, this.maxBufferPx,
|
this._scrollStrategy = new CipherListVirtualScrollStrategy(
|
||||||
this.checkAndUpdateItemSize);
|
this.itemSize,
|
||||||
}
|
this.minBufferPx,
|
||||||
|
this.maxBufferPx,
|
||||||
checkAndUpdateItemSize = () => {
|
this.checkAndUpdateItemSize
|
||||||
const sampleItem = document.querySelector('cdk-virtual-scroll-viewport .virtual-scroll-item') as HTMLElement;
|
);
|
||||||
const newItemSize = sampleItem?.offsetHeight;
|
}
|
||||||
|
|
||||||
if (newItemSize != null && newItemSize !== this.itemSize) {
|
checkAndUpdateItemSize = () => {
|
||||||
this.itemSize = newItemSize;
|
const sampleItem = document.querySelector(
|
||||||
this._scrollStrategy.updateItemAndBufferSize(this.itemSize, this.minBufferPx, this.maxBufferPx);
|
"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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,14 @@
|
||||||
import {
|
import { Directive, ElementRef, HostListener, Input } from "@angular/core";
|
||||||
Directive,
|
|
||||||
ElementRef,
|
|
||||||
HostListener,
|
|
||||||
Input,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[appFallbackSrc]',
|
selector: "[appFallbackSrc]",
|
||||||
})
|
})
|
||||||
export class FallbackSrcDirective {
|
export class FallbackSrcDirective {
|
||||||
@Input('appFallbackSrc') appFallbackSrc: string;
|
@Input("appFallbackSrc") appFallbackSrc: string;
|
||||||
|
|
||||||
constructor(private el: ElementRef) {
|
constructor(private el: ElementRef) {}
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('error') onError() {
|
@HostListener("error") onError() {
|
||||||
this.el.nativeElement.src = this.appFallbackSrc;
|
this.el.nativeElement.src = this.appFallbackSrc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,32 @@
|
||||||
import {
|
import { Directive, ElementRef, Input, Renderer2 } from "@angular/core";
|
||||||
Directive,
|
|
||||||
ElementRef,
|
|
||||||
Input,
|
|
||||||
Renderer2,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[appInputVerbatim]',
|
selector: "[appInputVerbatim]",
|
||||||
})
|
})
|
||||||
export class InputVerbatimDirective {
|
export class InputVerbatimDirective {
|
||||||
@Input() set appInputVerbatim(condition: boolean | string) {
|
@Input() set appInputVerbatim(condition: boolean | string) {
|
||||||
this.disableComplete = condition === '' || condition === true;
|
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");
|
||||||
}
|
}
|
||||||
|
if (!this.el.nativeElement.hasAttribute("autocapitalize")) {
|
||||||
private disableComplete: boolean;
|
this.renderer.setAttribute(this.el.nativeElement, "autocapitalize", "none");
|
||||||
|
|
||||||
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("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");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,37 @@
|
||||||
import {
|
import { Directive, ElementRef, HostListener } from "@angular/core";
|
||||||
Directive,
|
|
||||||
ElementRef,
|
|
||||||
HostListener,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[appSelectCopy]',
|
selector: "[appSelectCopy]",
|
||||||
})
|
})
|
||||||
export class SelectCopyDirective {
|
export class SelectCopyDirective {
|
||||||
constructor(private el: ElementRef, private platformUtilsService: PlatformUtilsService) { }
|
constructor(private el: ElementRef, private platformUtilsService: PlatformUtilsService) {}
|
||||||
|
|
||||||
@HostListener('copy') onCopy() {
|
@HostListener("copy") onCopy() {
|
||||||
if (window == null) {
|
if (window == null) {
|
||||||
return;
|
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 });
|
|
||||||
}
|
}
|
||||||
|
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 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
import {
|
import { Directive, HostListener } from "@angular/core";
|
||||||
Directive,
|
|
||||||
HostListener,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[appStopClick]',
|
selector: "[appStopClick]",
|
||||||
})
|
})
|
||||||
export class StopClickDirective {
|
export class StopClickDirective {
|
||||||
@HostListener('click', ['$event']) onClick($event: MouseEvent) {
|
@HostListener("click", ["$event"]) onClick($event: MouseEvent) {
|
||||||
$event.preventDefault();
|
$event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
import {
|
import { Directive, HostListener } from "@angular/core";
|
||||||
Directive,
|
|
||||||
HostListener,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[appStopProp]',
|
selector: "[appStopProp]",
|
||||||
})
|
})
|
||||||
export class StopPropDirective {
|
export class StopPropDirective {
|
||||||
@HostListener('click', ['$event']) onClick($event: MouseEvent) {
|
@HostListener("click", ["$event"]) onClick($event: MouseEvent) {
|
||||||
$event.stopPropagation();
|
$event.stopPropagation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +1,49 @@
|
||||||
import {
|
import { Directive, ElementRef, forwardRef, HostListener, Input, Renderer2 } from "@angular/core";
|
||||||
Directive,
|
import { ControlValueAccessor, NgControl, NG_VALUE_ACCESSOR } from "@angular/forms";
|
||||||
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/
|
// ref: https://juristr.com/blog/2018/02/ng-true-value-directive/
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: 'input[type=checkbox][appTrueFalseValue]',
|
selector: "input[type=checkbox][appTrueFalseValue]",
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
provide: NG_VALUE_ACCESSOR,
|
provide: NG_VALUE_ACCESSOR,
|
||||||
useExisting: forwardRef(() => TrueFalseValueDirective),
|
useExisting: forwardRef(() => TrueFalseValueDirective),
|
||||||
multi: true,
|
multi: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class TrueFalseValueDirective implements ControlValueAccessor {
|
export class TrueFalseValueDirective implements ControlValueAccessor {
|
||||||
@Input() trueValue = true;
|
@Input() trueValue = true;
|
||||||
@Input() falseValue = false;
|
@Input() falseValue = false;
|
||||||
|
|
||||||
constructor(private elementRef: ElementRef, private renderer: Renderer2) { }
|
constructor(private elementRef: ElementRef, private renderer: Renderer2) {}
|
||||||
|
|
||||||
@HostListener('change', ['$event'])
|
@HostListener("change", ["$event"])
|
||||||
onHostChange(ev: any) {
|
onHostChange(ev: any) {
|
||||||
this.propagateChange(ev.target.checked ? this.trueValue : this.falseValue);
|
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 {
|
registerOnChange(fn: any): void {
|
||||||
if (obj === this.trueValue) {
|
this.propagateChange = fn;
|
||||||
this.renderer.setProperty(this.elementRef.nativeElement, 'checked', true);
|
}
|
||||||
} else {
|
|
||||||
this.renderer.setProperty(this.elementRef.nativeElement, 'checked', false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
registerOnChange(fn: any): void {
|
registerOnTouched(fn: any): void {
|
||||||
this.propagateChange = fn;
|
/* nothing */
|
||||||
}
|
}
|
||||||
|
|
||||||
registerOnTouched(fn: any): void { /* nothing */ }
|
setDisabledState?(isDisabled: boolean): void {
|
||||||
|
/* nothing */
|
||||||
|
}
|
||||||
|
|
||||||
setDisabledState?(isDisabled: boolean): void { /* nothing */ }
|
private propagateChange = (_: any) => {
|
||||||
|
/* nothing */
|
||||||
private propagateChange = (_: any) => { /* nothing */ };
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,53 +1,50 @@
|
||||||
import {
|
import { Pipe, PipeTransform } from "@angular/core";
|
||||||
Pipe,
|
import { Utils } from "jslib-common/misc/utils";
|
||||||
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)
|
An updated pipe that sanitizes HTML, highlights numbers and special characters (in different colors each)
|
||||||
and handles Unicode / Emoji characters correctly.
|
and handles Unicode / Emoji characters correctly.
|
||||||
*/
|
*/
|
||||||
@Pipe({ name: 'colorPassword' })
|
@Pipe({ name: "colorPassword" })
|
||||||
export class ColorPasswordPipe implements PipeTransform {
|
export class ColorPasswordPipe implements PipeTransform {
|
||||||
transform(password: string) {
|
transform(password: string) {
|
||||||
// Convert to an array to handle cases that stings have special characters, ie: emoji.
|
// Convert to an array to handle cases that stings have special characters, ie: emoji.
|
||||||
const passwordArray = Array.from(password);
|
const passwordArray = Array.from(password);
|
||||||
let colorizedPassword = '';
|
let colorizedPassword = "";
|
||||||
for (let i = 0; i < passwordArray.length; i++) {
|
for (let i = 0; i < passwordArray.length; i++) {
|
||||||
let character = passwordArray[i];
|
let character = passwordArray[i];
|
||||||
let isSpecial = false;
|
let isSpecial = false;
|
||||||
// Sanitize HTML first.
|
// Sanitize HTML first.
|
||||||
switch (character) {
|
switch (character) {
|
||||||
case '&':
|
case "&":
|
||||||
character = '&';
|
character = "&";
|
||||||
isSpecial = true;
|
isSpecial = true;
|
||||||
break;
|
break;
|
||||||
case '<':
|
case "<":
|
||||||
character = '<';
|
character = "<";
|
||||||
isSpecial = true;
|
isSpecial = true;
|
||||||
break;
|
break;
|
||||||
case '>':
|
case ">":
|
||||||
character = '>';
|
character = ">";
|
||||||
isSpecial = true;
|
isSpecial = true;
|
||||||
break;
|
break;
|
||||||
case ' ':
|
case " ":
|
||||||
character = ' ';
|
character = " ";
|
||||||
isSpecial = true;
|
isSpecial = true;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let type = 'letter';
|
let type = "letter";
|
||||||
if (character.match(Utils.regexpEmojiPresentation)) {
|
if (character.match(Utils.regexpEmojiPresentation)) {
|
||||||
type = 'emoji';
|
type = "emoji";
|
||||||
} else if (isSpecial || character.match(/[^\w ]/)) {
|
} else if (isSpecial || character.match(/[^\w ]/)) {
|
||||||
type = 'special';
|
type = "special";
|
||||||
} else if (character.match(/\d/)) {
|
} else if (character.match(/\d/)) {
|
||||||
type = 'number';
|
type = "number";
|
||||||
}
|
}
|
||||||
colorizedPassword += '<span class="password-' + type + '">' + character + '</span>';
|
colorizedPassword += '<span class="password-' + type + '">' + character + "</span>";
|
||||||
}
|
|
||||||
return colorizedPassword;
|
|
||||||
}
|
}
|
||||||
|
return colorizedPassword;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
import {
|
import { Pipe, PipeTransform } from "@angular/core";
|
||||||
Pipe,
|
|
||||||
PipeTransform,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'i18n',
|
name: "i18n",
|
||||||
})
|
})
|
||||||
export class I18nPipe implements PipeTransform {
|
export class I18nPipe implements PipeTransform {
|
||||||
constructor(private i18nService: I18nService) { }
|
constructor(private i18nService: I18nService) {}
|
||||||
|
|
||||||
transform(id: string, p1?: string, p2?: string, p3?: string): string {
|
transform(id: string, p1?: string, p2?: string, p3?: string): string {
|
||||||
return this.i18nService.t(id, p1, p2, p3);
|
return this.i18nService.t(id, p1, p2, p3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,44 +1,41 @@
|
||||||
import {
|
import { Pipe, PipeTransform } from "@angular/core";
|
||||||
Pipe,
|
|
||||||
PipeTransform,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'searchCiphers',
|
name: "searchCiphers",
|
||||||
})
|
})
|
||||||
export class SearchCiphersPipe implements PipeTransform {
|
export class SearchCiphersPipe implements PipeTransform {
|
||||||
transform(ciphers: CipherView[], searchText: string, deleted: boolean = false): CipherView[] {
|
transform(ciphers: CipherView[], searchText: string, deleted: boolean = false): CipherView[] {
|
||||||
if (ciphers == null || ciphers.length === 0) {
|
if (ciphers == null || ciphers.length === 0) {
|
||||||
return [];
|
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;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +1,48 @@
|
||||||
import {
|
import { Pipe, PipeTransform } from "@angular/core";
|
||||||
Pipe,
|
|
||||||
PipeTransform,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'search',
|
name: "search",
|
||||||
})
|
})
|
||||||
export class SearchPipe implements PipeTransform {
|
export class SearchPipe implements PipeTransform {
|
||||||
transform(items: any[], searchText: string, prop1?: string, prop2?: string, prop3?: string): any[] {
|
transform(
|
||||||
if (items == null || items.length === 0) {
|
items: any[],
|
||||||
return [];
|
searchText: string,
|
||||||
}
|
prop1?: string,
|
||||||
|
prop2?: string,
|
||||||
if (searchText == null || searchText.length < 2) {
|
prop3?: string
|
||||||
return items;
|
): any[] {
|
||||||
}
|
if (items == null || items.length === 0) {
|
||||||
|
return [];
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,19 @@
|
||||||
import {
|
import { Pipe, PipeTransform } from "@angular/core";
|
||||||
Pipe,
|
|
||||||
PipeTransform,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
name?: string;
|
name?: string;
|
||||||
email: string;
|
email: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'userName',
|
name: "userName",
|
||||||
})
|
})
|
||||||
export class UserNamePipe implements PipeTransform {
|
export class UserNamePipe implements PipeTransform {
|
||||||
transform(user?: User): string {
|
transform(user?: User): string {
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
return user.name == null || user.name.trim() === '' ? user.email : user.name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return user.name == null || user.name.trim() === "" ? user.email : user.name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,90 +1,89 @@
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Open Sans';
|
font-family: "Open Sans";
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-display: auto;
|
font-display: auto;
|
||||||
src: url(webfonts/Open_Sans-italic-300.woff) format('woff');
|
src: url(webfonts/Open_Sans-italic-300.woff) format("woff");
|
||||||
unicode-range: U+0-10FFFF;
|
unicode-range: U+0-10FFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Open Sans';
|
font-family: "Open Sans";
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-display: auto;
|
font-display: auto;
|
||||||
src: url(webfonts/Open_Sans-italic-400.woff) format('woff');
|
src: url(webfonts/Open_Sans-italic-400.woff) format("woff");
|
||||||
unicode-range: U+0-10FFFF;
|
unicode-range: U+0-10FFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Open Sans';
|
font-family: "Open Sans";
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-display: auto;
|
font-display: auto;
|
||||||
src: url(webfonts/Open_Sans-italic-600.woff) format('woff');
|
src: url(webfonts/Open_Sans-italic-600.woff) format("woff");
|
||||||
unicode-range: U+0-10FFFF;
|
unicode-range: U+0-10FFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Open Sans';
|
font-family: "Open Sans";
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-display: auto;
|
font-display: auto;
|
||||||
src: url(webfonts/Open_Sans-italic-700.woff) format('woff');
|
src: url(webfonts/Open_Sans-italic-700.woff) format("woff");
|
||||||
unicode-range: U+0-10FFFF;
|
unicode-range: U+0-10FFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Open Sans';
|
font-family: "Open Sans";
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
font-display: auto;
|
font-display: auto;
|
||||||
src: url(webfonts/Open_Sans-italic-800.woff) format('woff');
|
src: url(webfonts/Open_Sans-italic-800.woff) format("woff");
|
||||||
unicode-range: U+0-10FFFF;
|
unicode-range: U+0-10FFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Open Sans';
|
font-family: "Open Sans";
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-display: auto;
|
font-display: auto;
|
||||||
src: url(webfonts/Open_Sans-normal-300.woff) format('woff');
|
src: url(webfonts/Open_Sans-normal-300.woff) format("woff");
|
||||||
unicode-range: U+0-10FFFF;
|
unicode-range: U+0-10FFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Open Sans';
|
font-family: "Open Sans";
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-display: auto;
|
font-display: auto;
|
||||||
src: url(webfonts/Open_Sans-normal-400.woff) format('woff');
|
src: url(webfonts/Open_Sans-normal-400.woff) format("woff");
|
||||||
unicode-range: U+0-10FFFF;
|
unicode-range: U+0-10FFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Open Sans';
|
font-family: "Open Sans";
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-display: auto;
|
font-display: auto;
|
||||||
src: url(webfonts/Open_Sans-normal-600.woff) format('woff');
|
src: url(webfonts/Open_Sans-normal-600.woff) format("woff");
|
||||||
unicode-range: U+0-10FFFF;
|
unicode-range: U+0-10FFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Open Sans';
|
font-family: "Open Sans";
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-display: auto;
|
font-display: auto;
|
||||||
src: url(webfonts/Open_Sans-normal-700.woff) format('woff');
|
src: url(webfonts/Open_Sans-normal-700.woff) format("woff");
|
||||||
unicode-range: U+0-10FFFF;
|
unicode-range: U+0-10FFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Open Sans';
|
font-family: "Open Sans";
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
font-display: auto;
|
font-display: auto;
|
||||||
src: url(webfonts/Open_Sans-normal-800.woff) format('woff');
|
src: url(webfonts/Open_Sans-normal-800.woff) format("woff");
|
||||||
unicode-range: U+0-10FFFF;
|
unicode-range: U+0-10FFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,43 +1,45 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from "@angular/core";
|
||||||
import {
|
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from "@angular/router";
|
||||||
ActivatedRouteSnapshot,
|
|
||||||
CanActivate,
|
|
||||||
Router,
|
|
||||||
RouterStateSnapshot,
|
|
||||||
} from '@angular/router';
|
|
||||||
|
|
||||||
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
|
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthGuardService implements CanActivate {
|
export class AuthGuardService implements CanActivate {
|
||||||
constructor(private vaultTimeoutService: VaultTimeoutService, private router: Router,
|
constructor(
|
||||||
private messagingService: MessagingService, private keyConnectorService: KeyConnectorService,
|
private vaultTimeoutService: VaultTimeoutService,
|
||||||
private stateService: StateService) { }
|
private router: Router,
|
||||||
|
private messagingService: MessagingService,
|
||||||
|
private keyConnectorService: KeyConnectorService,
|
||||||
|
private stateService: StateService
|
||||||
|
) {}
|
||||||
|
|
||||||
async canActivate(route: ActivatedRouteSnapshot, routerState: RouterStateSnapshot) {
|
async canActivate(route: ActivatedRouteSnapshot, routerState: RouterStateSnapshot) {
|
||||||
const isAuthed = await this.stateService.getIsAuthenticated();
|
const isAuthed = await this.stateService.getIsAuthenticated();
|
||||||
if (!isAuthed) {
|
if (!isAuthed) {
|
||||||
this.messagingService.send('authBlocked');
|
this.messagingService.send("authBlocked");
|
||||||
return false;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
@Injectable()
|
||||||
export class BroadcasterService extends BaseBroadcasterService {
|
export class BroadcasterService extends BaseBroadcasterService {}
|
||||||
}
|
|
||||||
|
|
|
@ -1,480 +1,452 @@
|
||||||
import {
|
import { Injector, LOCALE_ID, NgModule } from "@angular/core";
|
||||||
Injector,
|
|
||||||
LOCALE_ID,
|
|
||||||
NgModule,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/services/api.service';
|
import { ApiService } from "jslib-common/services/api.service";
|
||||||
import { AppIdService } from 'jslib-common/services/appId.service';
|
import { AppIdService } from "jslib-common/services/appId.service";
|
||||||
import { AuditService } from 'jslib-common/services/audit.service';
|
import { AuditService } from "jslib-common/services/audit.service";
|
||||||
import { AuthService } from 'jslib-common/services/auth.service';
|
import { AuthService } from "jslib-common/services/auth.service";
|
||||||
import { CipherService } from 'jslib-common/services/cipher.service';
|
import { CipherService } from "jslib-common/services/cipher.service";
|
||||||
import { CollectionService } from 'jslib-common/services/collection.service';
|
import { CollectionService } from "jslib-common/services/collection.service";
|
||||||
import { ConsoleLogService } from 'jslib-common/services/consoleLog.service';
|
import { ConsoleLogService } from "jslib-common/services/consoleLog.service";
|
||||||
import { CryptoService } from 'jslib-common/services/crypto.service';
|
import { CryptoService } from "jslib-common/services/crypto.service";
|
||||||
import { EnvironmentService } from 'jslib-common/services/environment.service';
|
import { EnvironmentService } from "jslib-common/services/environment.service";
|
||||||
import { EventService } from 'jslib-common/services/event.service';
|
import { EventService } from "jslib-common/services/event.service";
|
||||||
import { ExportService } from 'jslib-common/services/export.service';
|
import { ExportService } from "jslib-common/services/export.service";
|
||||||
import { FileUploadService } from 'jslib-common/services/fileUpload.service';
|
import { FileUploadService } from "jslib-common/services/fileUpload.service";
|
||||||
import { FolderService } from 'jslib-common/services/folder.service';
|
import { FolderService } from "jslib-common/services/folder.service";
|
||||||
import { KeyConnectorService } from 'jslib-common/services/keyConnector.service';
|
import { KeyConnectorService } from "jslib-common/services/keyConnector.service";
|
||||||
import { NotificationsService } from 'jslib-common/services/notifications.service';
|
import { NotificationsService } from "jslib-common/services/notifications.service";
|
||||||
import { OrganizationService } from 'jslib-common/services/organization.service';
|
import { OrganizationService } from "jslib-common/services/organization.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/services/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/services/passwordGeneration.service";
|
||||||
import { PolicyService } from 'jslib-common/services/policy.service';
|
import { PolicyService } from "jslib-common/services/policy.service";
|
||||||
import { ProviderService } from 'jslib-common/services/provider.service';
|
import { ProviderService } from "jslib-common/services/provider.service";
|
||||||
import { SearchService } from 'jslib-common/services/search.service';
|
import { SearchService } from "jslib-common/services/search.service";
|
||||||
import { SendService } from 'jslib-common/services/send.service';
|
import { SendService } from "jslib-common/services/send.service";
|
||||||
import { SettingsService } from 'jslib-common/services/settings.service';
|
import { SettingsService } from "jslib-common/services/settings.service";
|
||||||
import { StateService } from 'jslib-common/services/state.service';
|
import { StateService } from "jslib-common/services/state.service";
|
||||||
import { StateMigrationService } from 'jslib-common/services/stateMigration.service';
|
import { StateMigrationService } from "jslib-common/services/stateMigration.service";
|
||||||
import { SyncService } from 'jslib-common/services/sync.service';
|
import { SyncService } from "jslib-common/services/sync.service";
|
||||||
import { TokenService } from 'jslib-common/services/token.service';
|
import { TokenService } from "jslib-common/services/token.service";
|
||||||
import { TotpService } from 'jslib-common/services/totp.service';
|
import { TotpService } from "jslib-common/services/totp.service";
|
||||||
import { UserVerificationService } from 'jslib-common/services/userVerification.service';
|
import { UserVerificationService } from "jslib-common/services/userVerification.service";
|
||||||
import { VaultTimeoutService } from 'jslib-common/services/vaultTimeout.service';
|
import { VaultTimeoutService } from "jslib-common/services/vaultTimeout.service";
|
||||||
import { WebCryptoFunctionService } from 'jslib-common/services/webCryptoFunction.service';
|
import { WebCryptoFunctionService } from "jslib-common/services/webCryptoFunction.service";
|
||||||
|
|
||||||
import { ApiService as ApiServiceAbstraction } from 'jslib-common/abstractions/api.service';
|
import { ApiService as ApiServiceAbstraction } from "jslib-common/abstractions/api.service";
|
||||||
import { AppIdService as AppIdServiceAbstraction } from 'jslib-common/abstractions/appId.service';
|
import { AppIdService as AppIdServiceAbstraction } from "jslib-common/abstractions/appId.service";
|
||||||
import { AuditService as AuditServiceAbstraction } from 'jslib-common/abstractions/audit.service';
|
import { AuditService as AuditServiceAbstraction } from "jslib-common/abstractions/audit.service";
|
||||||
import { AuthService as AuthServiceAbstraction } from 'jslib-common/abstractions/auth.service';
|
import { AuthService as AuthServiceAbstraction } from "jslib-common/abstractions/auth.service";
|
||||||
import { BroadcasterService as BroadcasterServiceAbstraction } from 'jslib-common/abstractions/broadcaster.service';
|
import { BroadcasterService as BroadcasterServiceAbstraction } from "jslib-common/abstractions/broadcaster.service";
|
||||||
import { CipherService as CipherServiceAbstraction } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService as CipherServiceAbstraction } from "jslib-common/abstractions/cipher.service";
|
||||||
import { CollectionService as CollectionServiceAbstraction } from 'jslib-common/abstractions/collection.service';
|
import { CollectionService as CollectionServiceAbstraction } from "jslib-common/abstractions/collection.service";
|
||||||
import { CryptoService as CryptoServiceAbstraction } from 'jslib-common/abstractions/crypto.service';
|
import { CryptoService as CryptoServiceAbstraction } from "jslib-common/abstractions/crypto.service";
|
||||||
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib-common/abstractions/cryptoFunction.service';
|
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "jslib-common/abstractions/cryptoFunction.service";
|
||||||
import { EnvironmentService as EnvironmentServiceAbstraction } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService as EnvironmentServiceAbstraction } from "jslib-common/abstractions/environment.service";
|
||||||
import { EventService as EventServiceAbstraction } from 'jslib-common/abstractions/event.service';
|
import { EventService as EventServiceAbstraction } from "jslib-common/abstractions/event.service";
|
||||||
import { ExportService as ExportServiceAbstraction } from 'jslib-common/abstractions/export.service';
|
import { ExportService as ExportServiceAbstraction } from "jslib-common/abstractions/export.service";
|
||||||
import { FileUploadService as FileUploadServiceAbstraction } from 'jslib-common/abstractions/fileUpload.service';
|
import { FileUploadService as FileUploadServiceAbstraction } from "jslib-common/abstractions/fileUpload.service";
|
||||||
import { FolderService as FolderServiceAbstraction } from 'jslib-common/abstractions/folder.service';
|
import { FolderService as FolderServiceAbstraction } from "jslib-common/abstractions/folder.service";
|
||||||
import { I18nService as I18nServiceAbstraction } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService as I18nServiceAbstraction } from "jslib-common/abstractions/i18n.service";
|
||||||
import { KeyConnectorService as KeyConnectorServiceAbstraction } from 'jslib-common/abstractions/keyConnector.service';
|
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "jslib-common/abstractions/keyConnector.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { MessagingService as MessagingServiceAbstraction } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService as MessagingServiceAbstraction } from "jslib-common/abstractions/messaging.service";
|
||||||
import { NotificationsService as NotificationsServiceAbstraction } from 'jslib-common/abstractions/notifications.service';
|
import { NotificationsService as NotificationsServiceAbstraction } from "jslib-common/abstractions/notifications.service";
|
||||||
import { OrganizationService as OrganizationServiceAbstraction } from 'jslib-common/abstractions/organization.service';
|
import { OrganizationService as OrganizationServiceAbstraction } from "jslib-common/abstractions/organization.service";
|
||||||
import {
|
import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
PasswordGenerationService as PasswordGenerationServiceAbstraction,
|
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "jslib-common/abstractions/passwordReprompt.service";
|
||||||
} from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from 'jslib-common/abstractions/passwordReprompt.service';
|
import { PolicyService as PolicyServiceAbstraction } from "jslib-common/abstractions/policy.service";
|
||||||
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib-common/abstractions/platformUtils.service';
|
import { ProviderService as ProviderServiceAbstraction } from "jslib-common/abstractions/provider.service";
|
||||||
import { PolicyService as PolicyServiceAbstraction } from 'jslib-common/abstractions/policy.service';
|
import { SearchService as SearchServiceAbstraction } from "jslib-common/abstractions/search.service";
|
||||||
import { ProviderService as ProviderServiceAbstraction } from 'jslib-common/abstractions/provider.service';
|
import { SendService as SendServiceAbstraction } from "jslib-common/abstractions/send.service";
|
||||||
import { SearchService as SearchServiceAbstraction } from 'jslib-common/abstractions/search.service';
|
import { SettingsService as SettingsServiceAbstraction } from "jslib-common/abstractions/settings.service";
|
||||||
import { SendService as SendServiceAbstraction } from 'jslib-common/abstractions/send.service';
|
import { StateService as StateServiceAbstraction } from "jslib-common/abstractions/state.service";
|
||||||
import { SettingsService as SettingsServiceAbstraction } from 'jslib-common/abstractions/settings.service';
|
import { StateMigrationService as StateMigrationServiceAbstraction } from "jslib-common/abstractions/stateMigration.service";
|
||||||
import { StateService as StateServiceAbstraction } from 'jslib-common/abstractions/state.service';
|
import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service";
|
||||||
import { StateMigrationService as StateMigrationServiceAbstraction } from 'jslib-common/abstractions/stateMigration.service';
|
import { SyncService as SyncServiceAbstraction } from "jslib-common/abstractions/sync.service";
|
||||||
import { StorageService as StorageServiceAbstraction } from 'jslib-common/abstractions/storage.service';
|
import { TokenService as TokenServiceAbstraction } from "jslib-common/abstractions/token.service";
|
||||||
import { SyncService as SyncServiceAbstraction } from 'jslib-common/abstractions/sync.service';
|
import { TotpService as TotpServiceAbstraction } from "jslib-common/abstractions/totp.service";
|
||||||
import { TokenService as TokenServiceAbstraction } from 'jslib-common/abstractions/token.service';
|
import { UserVerificationService as UserVerificationServiceAbstraction } from "jslib-common/abstractions/userVerification.service";
|
||||||
import { TotpService as TotpServiceAbstraction } from 'jslib-common/abstractions/totp.service';
|
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "jslib-common/abstractions/vaultTimeout.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 { AuthGuardService } from "./auth-guard.service";
|
||||||
import { BroadcasterService } from './broadcaster.service';
|
import { BroadcasterService } from "./broadcaster.service";
|
||||||
import { LockGuardService } from './lock-guard.service';
|
import { LockGuardService } from "./lock-guard.service";
|
||||||
import { ModalService } from './modal.service';
|
import { ModalService } from "./modal.service";
|
||||||
import { PasswordRepromptService } from './passwordReprompt.service';
|
import { PasswordRepromptService } from "./passwordReprompt.service";
|
||||||
import { UnauthGuardService } from './unauth-guard.service';
|
import { UnauthGuardService } from "./unauth-guard.service";
|
||||||
import { ValidationService } from './validation.service';
|
import { ValidationService } from "./validation.service";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [],
|
declarations: [],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: 'WINDOW', useValue: window },
|
{ provide: "WINDOW", useValue: window },
|
||||||
{
|
{
|
||||||
provide: LOCALE_ID,
|
provide: LOCALE_ID,
|
||||||
useFactory: (i18nService: I18nServiceAbstraction) => i18nService.translationLocale,
|
useFactory: (i18nService: I18nServiceAbstraction) => i18nService.translationLocale,
|
||||||
deps: [I18nServiceAbstraction],
|
deps: [I18nServiceAbstraction],
|
||||||
},
|
},
|
||||||
ValidationService,
|
ValidationService,
|
||||||
AuthGuardService,
|
AuthGuardService,
|
||||||
UnauthGuardService,
|
UnauthGuardService,
|
||||||
LockGuardService,
|
LockGuardService,
|
||||||
ModalService,
|
ModalService,
|
||||||
{
|
{
|
||||||
provide: AppIdServiceAbstraction,
|
provide: AppIdServiceAbstraction,
|
||||||
useClass: AppIdService,
|
useClass: AppIdService,
|
||||||
deps: [StorageServiceAbstraction],
|
deps: [StorageServiceAbstraction],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: AuditServiceAbstraction,
|
provide: AuditServiceAbstraction,
|
||||||
useClass: AuditService,
|
useClass: AuditService,
|
||||||
deps: [CryptoFunctionServiceAbstraction, ApiServiceAbstraction],
|
deps: [CryptoFunctionServiceAbstraction, ApiServiceAbstraction],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: AuthServiceAbstraction,
|
provide: AuthServiceAbstraction,
|
||||||
useClass: AuthService,
|
useClass: AuthService,
|
||||||
deps: [
|
deps: [
|
||||||
CryptoServiceAbstraction,
|
CryptoServiceAbstraction,
|
||||||
ApiServiceAbstraction,
|
ApiServiceAbstraction,
|
||||||
TokenServiceAbstraction,
|
TokenServiceAbstraction,
|
||||||
AppIdServiceAbstraction,
|
AppIdServiceAbstraction,
|
||||||
I18nServiceAbstraction,
|
I18nServiceAbstraction,
|
||||||
PlatformUtilsServiceAbstraction,
|
PlatformUtilsServiceAbstraction,
|
||||||
MessagingServiceAbstraction,
|
MessagingServiceAbstraction,
|
||||||
VaultTimeoutServiceAbstraction,
|
VaultTimeoutServiceAbstraction,
|
||||||
LogService,
|
LogService,
|
||||||
CryptoFunctionServiceAbstraction,
|
CryptoFunctionServiceAbstraction,
|
||||||
KeyConnectorServiceAbstraction,
|
KeyConnectorServiceAbstraction,
|
||||||
EnvironmentServiceAbstraction,
|
EnvironmentServiceAbstraction,
|
||||||
StateServiceAbstraction,
|
StateServiceAbstraction,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: CipherServiceAbstraction,
|
provide: CipherServiceAbstraction,
|
||||||
useFactory: (
|
useFactory: (
|
||||||
cryptoService: CryptoServiceAbstraction,
|
cryptoService: CryptoServiceAbstraction,
|
||||||
settingsService: SettingsServiceAbstraction,
|
settingsService: SettingsServiceAbstraction,
|
||||||
apiService: ApiServiceAbstraction,
|
apiService: ApiServiceAbstraction,
|
||||||
fileUploadService: FileUploadServiceAbstraction,
|
fileUploadService: FileUploadServiceAbstraction,
|
||||||
i18nService: I18nServiceAbstraction,
|
i18nService: I18nServiceAbstraction,
|
||||||
injector: Injector,
|
injector: Injector,
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
stateService: StateServiceAbstraction,
|
stateService: StateServiceAbstraction
|
||||||
) => new CipherService(
|
) =>
|
||||||
cryptoService,
|
new CipherService(
|
||||||
settingsService,
|
cryptoService,
|
||||||
apiService,
|
settingsService,
|
||||||
fileUploadService,
|
apiService,
|
||||||
i18nService,
|
fileUploadService,
|
||||||
() => injector.get(SearchServiceAbstraction),
|
i18nService,
|
||||||
logService,
|
() => injector.get(SearchServiceAbstraction),
|
||||||
stateService,
|
logService,
|
||||||
),
|
stateService
|
||||||
deps: [
|
),
|
||||||
CryptoServiceAbstraction,
|
deps: [
|
||||||
SettingsServiceAbstraction,
|
CryptoServiceAbstraction,
|
||||||
ApiServiceAbstraction,
|
SettingsServiceAbstraction,
|
||||||
FileUploadServiceAbstraction,
|
ApiServiceAbstraction,
|
||||||
I18nServiceAbstraction,
|
FileUploadServiceAbstraction,
|
||||||
Injector, // TODO: Get rid of this circular dependency!
|
I18nServiceAbstraction,
|
||||||
LogService,
|
Injector, // TODO: Get rid of this circular dependency!
|
||||||
StateServiceAbstraction,
|
LogService,
|
||||||
],
|
StateServiceAbstraction,
|
||||||
},
|
],
|
||||||
{
|
},
|
||||||
provide: FolderServiceAbstraction,
|
{
|
||||||
useClass: FolderService,
|
provide: FolderServiceAbstraction,
|
||||||
deps: [
|
useClass: FolderService,
|
||||||
CryptoServiceAbstraction,
|
deps: [
|
||||||
ApiServiceAbstraction,
|
CryptoServiceAbstraction,
|
||||||
I18nServiceAbstraction,
|
ApiServiceAbstraction,
|
||||||
CipherServiceAbstraction,
|
I18nServiceAbstraction,
|
||||||
StateServiceAbstraction,
|
CipherServiceAbstraction,
|
||||||
],
|
StateServiceAbstraction,
|
||||||
},
|
],
|
||||||
{ provide: LogService, useFactory: () => new ConsoleLogService(false) },
|
},
|
||||||
{
|
{ provide: LogService, useFactory: () => new ConsoleLogService(false) },
|
||||||
provide: CollectionServiceAbstraction,
|
{
|
||||||
useClass: CollectionService,
|
provide: CollectionServiceAbstraction,
|
||||||
deps: [
|
useClass: CollectionService,
|
||||||
CryptoServiceAbstraction,
|
deps: [CryptoServiceAbstraction, I18nServiceAbstraction, StateServiceAbstraction],
|
||||||
I18nServiceAbstraction,
|
},
|
||||||
StateServiceAbstraction,
|
{
|
||||||
],
|
provide: EnvironmentServiceAbstraction,
|
||||||
},
|
useClass: EnvironmentService,
|
||||||
{
|
deps: [StateServiceAbstraction],
|
||||||
provide: EnvironmentServiceAbstraction,
|
},
|
||||||
useClass: EnvironmentService,
|
{
|
||||||
deps: [StateServiceAbstraction],
|
provide: TotpServiceAbstraction,
|
||||||
},
|
useClass: TotpService,
|
||||||
{
|
deps: [CryptoFunctionServiceAbstraction, LogService, StateServiceAbstraction],
|
||||||
provide: TotpServiceAbstraction,
|
},
|
||||||
useClass: TotpService,
|
{ provide: TokenServiceAbstraction, useClass: TokenService, deps: [StateServiceAbstraction] },
|
||||||
deps: [
|
{
|
||||||
CryptoFunctionServiceAbstraction,
|
provide: CryptoServiceAbstraction,
|
||||||
LogService,
|
useClass: CryptoService,
|
||||||
StateServiceAbstraction,
|
deps: [
|
||||||
],
|
CryptoFunctionServiceAbstraction,
|
||||||
},
|
PlatformUtilsServiceAbstraction,
|
||||||
{ provide: TokenServiceAbstraction, useClass: TokenService, deps: [StateServiceAbstraction] },
|
LogService,
|
||||||
{
|
StateServiceAbstraction,
|
||||||
provide: CryptoServiceAbstraction,
|
],
|
||||||
useClass: CryptoService,
|
},
|
||||||
deps: [
|
{
|
||||||
CryptoFunctionServiceAbstraction,
|
provide: PasswordGenerationServiceAbstraction,
|
||||||
PlatformUtilsServiceAbstraction,
|
useClass: PasswordGenerationService,
|
||||||
LogService,
|
deps: [CryptoServiceAbstraction, PolicyServiceAbstraction, StateServiceAbstraction],
|
||||||
StateServiceAbstraction,
|
},
|
||||||
],
|
{
|
||||||
},
|
provide: ApiServiceAbstraction,
|
||||||
{
|
useFactory: (
|
||||||
provide: PasswordGenerationServiceAbstraction,
|
tokenService: TokenServiceAbstraction,
|
||||||
useClass: PasswordGenerationService,
|
platformUtilsService: PlatformUtilsServiceAbstraction,
|
||||||
deps: [
|
environmentService: EnvironmentServiceAbstraction,
|
||||||
CryptoServiceAbstraction,
|
messagingService: MessagingServiceAbstraction
|
||||||
PolicyServiceAbstraction,
|
) =>
|
||||||
StateServiceAbstraction,
|
new ApiService(
|
||||||
],
|
tokenService,
|
||||||
},
|
platformUtilsService,
|
||||||
{
|
environmentService,
|
||||||
provide: ApiServiceAbstraction,
|
async (expired: boolean) => messagingService.send("logout", { expired: expired })
|
||||||
useFactory: (tokenService: TokenServiceAbstraction, platformUtilsService: PlatformUtilsServiceAbstraction,
|
),
|
||||||
environmentService: EnvironmentServiceAbstraction, messagingService: MessagingServiceAbstraction) =>
|
deps: [
|
||||||
new ApiService(tokenService, platformUtilsService, environmentService,
|
TokenServiceAbstraction,
|
||||||
async (expired: boolean) => messagingService.send('logout', { expired: expired })),
|
PlatformUtilsServiceAbstraction,
|
||||||
deps: [
|
EnvironmentServiceAbstraction,
|
||||||
TokenServiceAbstraction,
|
MessagingServiceAbstraction,
|
||||||
PlatformUtilsServiceAbstraction,
|
],
|
||||||
EnvironmentServiceAbstraction,
|
},
|
||||||
MessagingServiceAbstraction,
|
{
|
||||||
],
|
provide: FileUploadServiceAbstraction,
|
||||||
},
|
useClass: FileUploadService,
|
||||||
{
|
deps: [LogService, ApiServiceAbstraction],
|
||||||
provide: FileUploadServiceAbstraction,
|
},
|
||||||
useClass: FileUploadService,
|
{
|
||||||
deps: [
|
provide: SyncServiceAbstraction,
|
||||||
LogService,
|
useFactory: (
|
||||||
ApiServiceAbstraction,
|
apiService: ApiServiceAbstraction,
|
||||||
],
|
settingsService: SettingsServiceAbstraction,
|
||||||
},
|
folderService: FolderServiceAbstraction,
|
||||||
{
|
cipherService: CipherServiceAbstraction,
|
||||||
provide: SyncServiceAbstraction,
|
cryptoService: CryptoServiceAbstraction,
|
||||||
useFactory: (
|
collectionService: CollectionServiceAbstraction,
|
||||||
apiService: ApiServiceAbstraction,
|
messagingService: MessagingServiceAbstraction,
|
||||||
settingsService: SettingsServiceAbstraction,
|
policyService: PolicyServiceAbstraction,
|
||||||
folderService: FolderServiceAbstraction,
|
sendService: SendServiceAbstraction,
|
||||||
cipherService: CipherServiceAbstraction,
|
logService: LogService,
|
||||||
cryptoService: CryptoServiceAbstraction,
|
keyConnectorService: KeyConnectorServiceAbstraction,
|
||||||
collectionService: CollectionServiceAbstraction,
|
stateService: StateServiceAbstraction,
|
||||||
messagingService: MessagingServiceAbstraction,
|
organizationService: OrganizationServiceAbstraction,
|
||||||
policyService: PolicyServiceAbstraction,
|
providerService: ProviderServiceAbstraction
|
||||||
sendService: SendServiceAbstraction,
|
) =>
|
||||||
logService: LogService,
|
new SyncService(
|
||||||
keyConnectorService: KeyConnectorServiceAbstraction,
|
apiService,
|
||||||
stateService: StateServiceAbstraction,
|
settingsService,
|
||||||
organizationService: OrganizationServiceAbstraction,
|
folderService,
|
||||||
providerService: ProviderServiceAbstraction,
|
cipherService,
|
||||||
) => new SyncService(
|
cryptoService,
|
||||||
apiService,
|
collectionService,
|
||||||
settingsService,
|
messagingService,
|
||||||
folderService,
|
policyService,
|
||||||
cipherService,
|
sendService,
|
||||||
cryptoService,
|
logService,
|
||||||
collectionService,
|
keyConnectorService,
|
||||||
messagingService,
|
stateService,
|
||||||
policyService,
|
organizationService,
|
||||||
sendService,
|
providerService,
|
||||||
logService,
|
async (expired: boolean) => messagingService.send("logout", { expired: expired })
|
||||||
keyConnectorService,
|
),
|
||||||
stateService,
|
deps: [
|
||||||
organizationService,
|
ApiServiceAbstraction,
|
||||||
providerService,
|
SettingsServiceAbstraction,
|
||||||
async (expired: boolean) => messagingService.send('logout', { expired: expired })),
|
FolderServiceAbstraction,
|
||||||
deps: [
|
CipherServiceAbstraction,
|
||||||
ApiServiceAbstraction,
|
CryptoServiceAbstraction,
|
||||||
SettingsServiceAbstraction,
|
CollectionServiceAbstraction,
|
||||||
FolderServiceAbstraction,
|
MessagingServiceAbstraction,
|
||||||
CipherServiceAbstraction,
|
PolicyServiceAbstraction,
|
||||||
CryptoServiceAbstraction,
|
SendServiceAbstraction,
|
||||||
CollectionServiceAbstraction,
|
LogService,
|
||||||
MessagingServiceAbstraction,
|
KeyConnectorServiceAbstraction,
|
||||||
PolicyServiceAbstraction,
|
StateServiceAbstraction,
|
||||||
SendServiceAbstraction,
|
OrganizationServiceAbstraction,
|
||||||
LogService,
|
ProviderServiceAbstraction,
|
||||||
KeyConnectorServiceAbstraction,
|
],
|
||||||
StateServiceAbstraction,
|
},
|
||||||
OrganizationServiceAbstraction,
|
{ provide: BroadcasterServiceAbstraction, useClass: BroadcasterService },
|
||||||
ProviderServiceAbstraction,
|
{
|
||||||
],
|
provide: SettingsServiceAbstraction,
|
||||||
},
|
useClass: SettingsService,
|
||||||
{ provide: BroadcasterServiceAbstraction, useClass: BroadcasterService },
|
deps: [StateServiceAbstraction],
|
||||||
{
|
},
|
||||||
provide: SettingsServiceAbstraction,
|
{
|
||||||
useClass: SettingsService,
|
provide: VaultTimeoutServiceAbstraction,
|
||||||
deps: [StateServiceAbstraction],
|
useFactory: (
|
||||||
},
|
cipherService: CipherServiceAbstraction,
|
||||||
{
|
folderService: FolderServiceAbstraction,
|
||||||
provide: VaultTimeoutServiceAbstraction,
|
collectionService: CollectionServiceAbstraction,
|
||||||
useFactory: (
|
cryptoService: CryptoServiceAbstraction,
|
||||||
cipherService: CipherServiceAbstraction,
|
platformUtilsService: PlatformUtilsServiceAbstraction,
|
||||||
folderService: FolderServiceAbstraction,
|
messagingService: MessagingServiceAbstraction,
|
||||||
collectionService: CollectionServiceAbstraction,
|
searchService: SearchServiceAbstraction,
|
||||||
cryptoService: CryptoServiceAbstraction,
|
tokenService: TokenServiceAbstraction,
|
||||||
platformUtilsService: PlatformUtilsServiceAbstraction,
|
policyService: PolicyServiceAbstraction,
|
||||||
messagingService: MessagingServiceAbstraction,
|
keyConnectorService: KeyConnectorServiceAbstraction,
|
||||||
searchService: SearchServiceAbstraction,
|
stateService: StateServiceAbstraction
|
||||||
tokenService: TokenServiceAbstraction,
|
) =>
|
||||||
policyService: PolicyServiceAbstraction,
|
new VaultTimeoutService(
|
||||||
keyConnectorService: KeyConnectorServiceAbstraction,
|
cipherService,
|
||||||
stateService: StateServiceAbstraction,
|
folderService,
|
||||||
) => new VaultTimeoutService(
|
collectionService,
|
||||||
cipherService,
|
cryptoService,
|
||||||
folderService,
|
platformUtilsService,
|
||||||
collectionService,
|
messagingService,
|
||||||
cryptoService,
|
searchService,
|
||||||
platformUtilsService,
|
tokenService,
|
||||||
messagingService,
|
policyService,
|
||||||
searchService,
|
keyConnectorService,
|
||||||
tokenService,
|
stateService,
|
||||||
policyService,
|
null,
|
||||||
keyConnectorService,
|
async () => messagingService.send("logout", { expired: false })
|
||||||
stateService,
|
),
|
||||||
null,
|
deps: [
|
||||||
async () => messagingService.send('logout', { expired: false }),
|
CipherServiceAbstraction,
|
||||||
),
|
FolderServiceAbstraction,
|
||||||
deps: [
|
CollectionServiceAbstraction,
|
||||||
CipherServiceAbstraction,
|
CryptoServiceAbstraction,
|
||||||
FolderServiceAbstraction,
|
PlatformUtilsServiceAbstraction,
|
||||||
CollectionServiceAbstraction,
|
MessagingServiceAbstraction,
|
||||||
CryptoServiceAbstraction,
|
SearchServiceAbstraction,
|
||||||
PlatformUtilsServiceAbstraction,
|
TokenServiceAbstraction,
|
||||||
MessagingServiceAbstraction,
|
PolicyServiceAbstraction,
|
||||||
SearchServiceAbstraction,
|
KeyConnectorServiceAbstraction,
|
||||||
TokenServiceAbstraction,
|
StateServiceAbstraction,
|
||||||
PolicyServiceAbstraction,
|
],
|
||||||
KeyConnectorServiceAbstraction,
|
},
|
||||||
StateServiceAbstraction,
|
{
|
||||||
],
|
provide: StateServiceAbstraction,
|
||||||
},
|
useClass: StateService,
|
||||||
{
|
deps: [
|
||||||
provide: StateServiceAbstraction,
|
StorageServiceAbstraction,
|
||||||
useClass: StateService,
|
"SECURE_STORAGE",
|
||||||
deps: [
|
LogService,
|
||||||
StorageServiceAbstraction,
|
StateMigrationServiceAbstraction,
|
||||||
'SECURE_STORAGE',
|
],
|
||||||
LogService,
|
},
|
||||||
StateMigrationServiceAbstraction,
|
{
|
||||||
],
|
provide: StateMigrationServiceAbstraction,
|
||||||
},
|
useClass: StateMigrationService,
|
||||||
{
|
deps: [StorageServiceAbstraction, "SECURE_STORAGE"],
|
||||||
provide: StateMigrationServiceAbstraction,
|
},
|
||||||
useClass: StateMigrationService,
|
{
|
||||||
deps: [
|
provide: ExportServiceAbstraction,
|
||||||
StorageServiceAbstraction,
|
useClass: ExportService,
|
||||||
'SECURE_STORAGE',
|
deps: [
|
||||||
],
|
FolderServiceAbstraction,
|
||||||
},
|
CipherServiceAbstraction,
|
||||||
{
|
ApiServiceAbstraction,
|
||||||
provide: ExportServiceAbstraction,
|
CryptoServiceAbstraction,
|
||||||
useClass: ExportService,
|
],
|
||||||
deps: [
|
},
|
||||||
FolderServiceAbstraction,
|
{
|
||||||
CipherServiceAbstraction,
|
provide: SearchServiceAbstraction,
|
||||||
ApiServiceAbstraction,
|
useClass: SearchService,
|
||||||
CryptoServiceAbstraction,
|
deps: [CipherServiceAbstraction, LogService, I18nServiceAbstraction],
|
||||||
],
|
},
|
||||||
},
|
{
|
||||||
{
|
provide: NotificationsServiceAbstraction,
|
||||||
provide: SearchServiceAbstraction,
|
useFactory: (
|
||||||
useClass: SearchService,
|
syncService: SyncServiceAbstraction,
|
||||||
deps: [
|
appIdService: AppIdServiceAbstraction,
|
||||||
CipherServiceAbstraction,
|
apiService: ApiServiceAbstraction,
|
||||||
LogService,
|
vaultTimeoutService: VaultTimeoutServiceAbstraction,
|
||||||
I18nServiceAbstraction,
|
environmentService: EnvironmentServiceAbstraction,
|
||||||
],
|
messagingService: MessagingServiceAbstraction,
|
||||||
},
|
logService: LogService,
|
||||||
{
|
stateService: StateServiceAbstraction
|
||||||
provide: NotificationsServiceAbstraction,
|
) =>
|
||||||
useFactory: (
|
new NotificationsService(
|
||||||
syncService: SyncServiceAbstraction,
|
syncService,
|
||||||
appIdService: AppIdServiceAbstraction,
|
appIdService,
|
||||||
apiService: ApiServiceAbstraction,
|
apiService,
|
||||||
vaultTimeoutService: VaultTimeoutServiceAbstraction,
|
vaultTimeoutService,
|
||||||
environmentService: EnvironmentServiceAbstraction,
|
environmentService,
|
||||||
messagingService: MessagingServiceAbstraction,
|
async () => messagingService.send("logout", { expired: true }),
|
||||||
logService: LogService,
|
logService,
|
||||||
stateService: StateServiceAbstraction,
|
stateService
|
||||||
) => new NotificationsService(
|
),
|
||||||
syncService,
|
deps: [
|
||||||
appIdService,
|
SyncServiceAbstraction,
|
||||||
apiService,
|
AppIdServiceAbstraction,
|
||||||
vaultTimeoutService,
|
ApiServiceAbstraction,
|
||||||
environmentService,
|
VaultTimeoutServiceAbstraction,
|
||||||
async () => messagingService.send('logout', { expired: true }),
|
EnvironmentServiceAbstraction,
|
||||||
logService,
|
MessagingServiceAbstraction,
|
||||||
stateService,
|
LogService,
|
||||||
),
|
StateServiceAbstraction,
|
||||||
deps: [
|
],
|
||||||
SyncServiceAbstraction,
|
},
|
||||||
AppIdServiceAbstraction,
|
{
|
||||||
ApiServiceAbstraction,
|
provide: CryptoFunctionServiceAbstraction,
|
||||||
VaultTimeoutServiceAbstraction,
|
useClass: WebCryptoFunctionService,
|
||||||
EnvironmentServiceAbstraction,
|
deps: ["WINDOW", PlatformUtilsServiceAbstraction],
|
||||||
MessagingServiceAbstraction,
|
},
|
||||||
LogService,
|
{
|
||||||
StateServiceAbstraction,
|
provide: EventServiceAbstraction,
|
||||||
],
|
useClass: EventService,
|
||||||
},
|
deps: [
|
||||||
{
|
ApiServiceAbstraction,
|
||||||
provide: CryptoFunctionServiceAbstraction,
|
CipherServiceAbstraction,
|
||||||
useClass: WebCryptoFunctionService,
|
StateServiceAbstraction,
|
||||||
deps: ['WINDOW', PlatformUtilsServiceAbstraction],
|
LogService,
|
||||||
},
|
OrganizationServiceAbstraction,
|
||||||
{
|
],
|
||||||
provide: EventServiceAbstraction,
|
},
|
||||||
useClass: EventService,
|
{
|
||||||
deps: [
|
provide: PolicyServiceAbstraction,
|
||||||
ApiServiceAbstraction,
|
useClass: PolicyService,
|
||||||
CipherServiceAbstraction,
|
deps: [StateServiceAbstraction, OrganizationServiceAbstraction, ApiServiceAbstraction],
|
||||||
StateServiceAbstraction,
|
},
|
||||||
LogService,
|
{
|
||||||
OrganizationServiceAbstraction,
|
provide: SendServiceAbstraction,
|
||||||
],
|
useClass: SendService,
|
||||||
},
|
deps: [
|
||||||
{
|
CryptoServiceAbstraction,
|
||||||
provide: PolicyServiceAbstraction,
|
ApiServiceAbstraction,
|
||||||
useClass: PolicyService,
|
FileUploadServiceAbstraction,
|
||||||
deps: [
|
I18nServiceAbstraction,
|
||||||
StateServiceAbstraction,
|
CryptoFunctionServiceAbstraction,
|
||||||
OrganizationServiceAbstraction,
|
StateServiceAbstraction,
|
||||||
ApiServiceAbstraction,
|
],
|
||||||
],
|
},
|
||||||
},
|
{
|
||||||
{
|
provide: KeyConnectorServiceAbstraction,
|
||||||
provide: SendServiceAbstraction,
|
useClass: KeyConnectorService,
|
||||||
useClass: SendService,
|
deps: [
|
||||||
deps: [
|
StateServiceAbstraction,
|
||||||
CryptoServiceAbstraction,
|
CryptoServiceAbstraction,
|
||||||
ApiServiceAbstraction,
|
ApiServiceAbstraction,
|
||||||
FileUploadServiceAbstraction,
|
TokenServiceAbstraction,
|
||||||
I18nServiceAbstraction,
|
LogService,
|
||||||
CryptoFunctionServiceAbstraction,
|
OrganizationServiceAbstraction,
|
||||||
StateServiceAbstraction,
|
],
|
||||||
],
|
},
|
||||||
},
|
{
|
||||||
{
|
provide: UserVerificationServiceAbstraction,
|
||||||
provide: KeyConnectorServiceAbstraction,
|
useClass: UserVerificationService,
|
||||||
useClass: KeyConnectorService,
|
deps: [CryptoServiceAbstraction, I18nServiceAbstraction, ApiServiceAbstraction],
|
||||||
deps: [
|
},
|
||||||
StateServiceAbstraction,
|
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
|
||||||
CryptoServiceAbstraction,
|
{
|
||||||
ApiServiceAbstraction,
|
provide: OrganizationServiceAbstraction,
|
||||||
TokenServiceAbstraction,
|
useClass: OrganizationService,
|
||||||
LogService,
|
deps: [StateServiceAbstraction],
|
||||||
OrganizationServiceAbstraction,
|
},
|
||||||
],
|
{
|
||||||
},
|
provide: ProviderServiceAbstraction,
|
||||||
{
|
useClass: ProviderService,
|
||||||
provide: UserVerificationServiceAbstraction,
|
deps: [StateServiceAbstraction],
|
||||||
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 {}
|
||||||
}
|
|
||||||
|
|
|
@ -1,29 +1,29 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from "@angular/core";
|
||||||
import {
|
import { CanActivate, Router } from "@angular/router";
|
||||||
CanActivate,
|
|
||||||
Router,
|
|
||||||
} from '@angular/router';
|
|
||||||
|
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LockGuardService implements CanActivate {
|
export class LockGuardService implements CanActivate {
|
||||||
protected homepage = 'vault';
|
protected homepage = "vault";
|
||||||
constructor(private vaultTimeoutService: VaultTimeoutService, private router: Router,
|
constructor(
|
||||||
private stateService: StateService) { }
|
private vaultTimeoutService: VaultTimeoutService,
|
||||||
|
private router: Router,
|
||||||
|
private stateService: StateService
|
||||||
|
) {}
|
||||||
|
|
||||||
async canActivate() {
|
async canActivate() {
|
||||||
if (!await this.stateService.getIsAuthenticated()) {
|
if (!(await this.stateService.getIsAuthenticated())) {
|
||||||
this.router.navigate(['login']);
|
this.router.navigate(["login"]);
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
if (!await this.vaultTimeoutService.isLocked()) {
|
|
||||||
this.router.navigate([this.homepage]);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(await this.vaultTimeoutService.isLocked())) {
|
||||||
|
this.router.navigate([this.homepage]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,164 +1,179 @@
|
||||||
import {
|
import {
|
||||||
ApplicationRef,
|
ApplicationRef,
|
||||||
ComponentFactory,
|
ComponentFactory,
|
||||||
ComponentFactoryResolver,
|
ComponentFactoryResolver,
|
||||||
ComponentRef,
|
ComponentRef,
|
||||||
EmbeddedViewRef,
|
EmbeddedViewRef,
|
||||||
Injectable,
|
Injectable,
|
||||||
Injector,
|
Injector,
|
||||||
Type,
|
Type,
|
||||||
ViewContainerRef
|
ViewContainerRef,
|
||||||
} from '@angular/core';
|
} from "@angular/core";
|
||||||
import { first } from 'rxjs/operators';
|
import { first } from "rxjs/operators";
|
||||||
|
|
||||||
import { DynamicModalComponent } from '../components/modal/dynamic-modal.component';
|
import { DynamicModalComponent } from "../components/modal/dynamic-modal.component";
|
||||||
import { ModalInjector } from '../components/modal/modal-injector';
|
import { ModalInjector } from "../components/modal/modal-injector";
|
||||||
import { ModalRef } from '../components/modal/modal.ref';
|
import { ModalRef } from "../components/modal/modal.ref";
|
||||||
|
|
||||||
export class ModalConfig<D = any> {
|
export class ModalConfig<D = any> {
|
||||||
data?: D;
|
data?: D;
|
||||||
allowMultipleModals: boolean = false;
|
allowMultipleModals: boolean = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ModalService {
|
export class ModalService {
|
||||||
protected modalList: ComponentRef<DynamicModalComponent>[] = [];
|
protected modalList: ComponentRef<DynamicModalComponent>[] = [];
|
||||||
|
|
||||||
// Lazy loaded modules are not available in componentFactoryResolver,
|
// Lazy loaded modules are not available in componentFactoryResolver,
|
||||||
// therefore modules needs to manually initialize their resolvers.
|
// therefore modules needs to manually initialize their resolvers.
|
||||||
private factoryResolvers: Map<Type<any>, ComponentFactoryResolver> = new Map();
|
private factoryResolvers: Map<Type<any>, ComponentFactoryResolver> = new Map();
|
||||||
|
|
||||||
constructor(private componentFactoryResolver: ComponentFactoryResolver, private applicationRef: ApplicationRef,
|
constructor(
|
||||||
private injector: Injector) {
|
private componentFactoryResolver: ComponentFactoryResolver,
|
||||||
document.addEventListener('keyup', event => {
|
private applicationRef: ApplicationRef,
|
||||||
if (event.key === 'Escape' && this.modalCount > 0) {
|
private injector: Injector
|
||||||
this.topModal.instance.close();
|
) {
|
||||||
}
|
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() {
|
// onClose is used in Web to hook into bootstrap. On other projects we pipe it directly to closed.
|
||||||
return this.modalList.length;
|
modalRef.onClose.pipe(first()).subscribe(() => {
|
||||||
}
|
modalRef.closed();
|
||||||
|
|
||||||
private get topModal() {
|
if (this.modalCount === 0) {
|
||||||
return this.modalList[this.modalCount - 1];
|
document.body.classList.remove("modal-open");
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async openViewRef<T>(componentType: Type<T>, viewContainerRef: ViewContainerRef,
|
protected createModalComponent(
|
||||||
setComponentParameters: (component: T) => void = null): Promise<[ModalRef, T]> {
|
config: ModalConfig
|
||||||
|
): [ModalRef, ComponentRef<DynamicModalComponent>] {
|
||||||
|
const modalRef = new ModalRef();
|
||||||
|
|
||||||
const [modalRef, modalComponentRef] = this.openInternal(componentType, null, false);
|
const map = new WeakMap();
|
||||||
modalComponentRef.instance.setComponentParameters = setComponentParameters;
|
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, componentRef];
|
||||||
|
}
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,40 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
|
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
||||||
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from 'jslib-common/abstractions/passwordReprompt.service';
|
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "jslib-common/abstractions/passwordReprompt.service";
|
||||||
|
|
||||||
import { PasswordRepromptComponent } from '../components/password-reprompt.component';
|
import { PasswordRepromptComponent } from "../components/password-reprompt.component";
|
||||||
import { ModalService } from './modal.service';
|
import { ModalService } from "./modal.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PasswordRepromptService implements PasswordRepromptServiceAbstraction {
|
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() {
|
protectedFields() {
|
||||||
return ['TOTP', 'Password', 'H_Field', 'Card Number', 'Security Code'];
|
return ["TOTP", "Password", "H_Field", "Card Number", "Security Code"];
|
||||||
|
}
|
||||||
|
|
||||||
|
async showPasswordPrompt() {
|
||||||
|
if (!(await this.enabled())) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async showPasswordPrompt() {
|
const ref = this.modalService.open(this.component, { allowMultipleModals: true });
|
||||||
if (!await this.enabled()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ref = this.modalService.open(this.component, {allowMultipleModals: true});
|
if (ref == null) {
|
||||||
|
return false;
|
||||||
if (ref == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await ref.onClosedPromise();
|
|
||||||
return result === true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async enabled() {
|
const result = await ref.onClosedPromise();
|
||||||
return !await this.keyConnectorService.getUsesKeyConnector();
|
return result === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async enabled() {
|
||||||
|
return !(await this.keyConnectorService.getUsesKeyConnector());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,29 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from "@angular/core";
|
||||||
import {
|
import { CanActivate, Router } from "@angular/router";
|
||||||
CanActivate,
|
|
||||||
Router,
|
|
||||||
} from '@angular/router';
|
|
||||||
|
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UnauthGuardService implements CanActivate {
|
export class UnauthGuardService implements CanActivate {
|
||||||
|
protected homepage = "vault";
|
||||||
|
constructor(
|
||||||
|
private vaultTimeoutService: VaultTimeoutService,
|
||||||
|
private router: Router,
|
||||||
|
private stateService: StateService
|
||||||
|
) {}
|
||||||
|
|
||||||
protected homepage = 'vault';
|
async canActivate() {
|
||||||
constructor(private vaultTimeoutService: VaultTimeoutService, private router: Router,
|
const isAuthed = await this.stateService.getIsAuthenticated();
|
||||||
private stateService: StateService) { }
|
if (isAuthed) {
|
||||||
|
const locked = await this.vaultTimeoutService.isLocked();
|
||||||
async canActivate() {
|
if (locked) {
|
||||||
const isAuthed = await this.stateService.getIsAuthenticated();
|
this.router.navigate(["lock"]);
|
||||||
if (isAuthed) {
|
} else {
|
||||||
const locked = await this.vaultTimeoutService.isLocked();
|
this.router.navigate([this.homepage]);
|
||||||
if (locked) {
|
}
|
||||||
this.router.navigate(['lock']);
|
return false;
|
||||||
} else {
|
|
||||||
this.router.navigate([this.homepage]);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +1,39 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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()
|
@Injectable()
|
||||||
export class ValidationService {
|
export class ValidationService {
|
||||||
constructor(private i18nService: I18nService, private platformUtilsService: PlatformUtilsService) { }
|
constructor(
|
||||||
|
private i18nService: I18nService,
|
||||||
|
private platformUtilsService: PlatformUtilsService
|
||||||
|
) {}
|
||||||
|
|
||||||
showError(data: any): string[] {
|
showError(data: any): string[] {
|
||||||
const defaultErrorMessage = this.i18nService.t('unexpectedError');
|
const defaultErrorMessage = this.i18nService.t("unexpectedError");
|
||||||
let errors: string[] = [];
|
let errors: string[] = [];
|
||||||
|
|
||||||
if (data != null && typeof data === 'string') {
|
if (data != null && typeof data === "string") {
|
||||||
errors.push(data);
|
errors.push(data);
|
||||||
} else if (data == null || typeof data !== 'object') {
|
} else if (data == null || typeof data !== "object") {
|
||||||
errors.push(defaultErrorMessage);
|
errors.push(defaultErrorMessage);
|
||||||
} else if (data.validationErrors != null) {
|
} else if (data.validationErrors != null) {
|
||||||
errors = errors.concat((data as ErrorResponse).getAllMessages());
|
errors = errors.concat((data as ErrorResponse).getAllMessages());
|
||||||
} else {
|
} else {
|
||||||
errors.push(data.message ? data.message : defaultErrorMessage);
|
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 (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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,17 +14,9 @@
|
||||||
"declarationDir": "dist/types",
|
"declarationDir": "dist/types",
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"paths": {
|
"paths": {
|
||||||
"jslib-common/*": [
|
"jslib-common/*": ["../common/src/*"]
|
||||||
"../common/src/*"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src", "spec"],
|
||||||
"src",
|
"exclude": ["node_modules", "dist"]
|
||||||
"spec"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules",
|
|
||||||
"dist"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,4 @@
|
||||||
export abstract class AppIdService {
|
export abstract class AppIdService {
|
||||||
getAppId: () => Promise<string>;
|
getAppId: () => Promise<string>;
|
||||||
getAnonymousAppId: () => Promise<string>;
|
getAnonymousAppId: () => Promise<string>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { BreachAccountResponse } from '../models/response/breachAccountResponse';
|
import { BreachAccountResponse } from "../models/response/breachAccountResponse";
|
||||||
|
|
||||||
export abstract class AuditService {
|
export abstract class AuditService {
|
||||||
passwordLeaked: (password: string) => Promise<number>;
|
passwordLeaked: (password: string) => Promise<number>;
|
||||||
breachedAccounts: (username: string) => Promise<BreachAccountResponse[]>;
|
breachedAccounts: (username: string) => Promise<BreachAccountResponse[]>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,60 @@
|
||||||
import { TwoFactorProviderType } from '../enums/twoFactorProviderType';
|
import { TwoFactorProviderType } from "../enums/twoFactorProviderType";
|
||||||
|
|
||||||
import { AuthResult } from '../models/domain/authResult';
|
import { AuthResult } from "../models/domain/authResult";
|
||||||
import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey';
|
import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
|
||||||
|
|
||||||
export abstract class AuthService {
|
export abstract class AuthService {
|
||||||
email: string;
|
email: string;
|
||||||
masterPasswordHash: string;
|
masterPasswordHash: string;
|
||||||
code: string;
|
code: string;
|
||||||
codeVerifier: string;
|
codeVerifier: string;
|
||||||
ssoRedirectUrl: string;
|
ssoRedirectUrl: string;
|
||||||
clientId: string;
|
clientId: string;
|
||||||
clientSecret: string;
|
clientSecret: string;
|
||||||
twoFactorProvidersData: Map<TwoFactorProviderType, { [key: string]: string; }>;
|
twoFactorProvidersData: Map<TwoFactorProviderType, { [key: string]: string }>;
|
||||||
selectedTwoFactorProviderType: TwoFactorProviderType;
|
selectedTwoFactorProviderType: TwoFactorProviderType;
|
||||||
|
|
||||||
logIn: (email: string, masterPassword: string, captchaToken?: string) => Promise<AuthResult>;
|
logIn: (email: string, masterPassword: string, captchaToken?: string) => Promise<AuthResult>;
|
||||||
logInSso: (code: string, codeVerifier: string, redirectUrl: string, orgId: string) => Promise<AuthResult>;
|
logInSso: (
|
||||||
logInApiKey: (clientId: string, clientSecret: string) => Promise<AuthResult>;
|
code: string,
|
||||||
logInTwoFactor: (twoFactorProvider: TwoFactorProviderType, twoFactorToken: string,
|
codeVerifier: string,
|
||||||
remember?: boolean) => Promise<AuthResult>;
|
redirectUrl: string,
|
||||||
logInComplete: (email: string, masterPassword: string, twoFactorProvider: TwoFactorProviderType,
|
orgId: string
|
||||||
twoFactorToken: string, remember?: boolean, captchaToken?: string) => Promise<AuthResult>;
|
) => Promise<AuthResult>;
|
||||||
logInSsoComplete: (code: string, codeVerifier: string, redirectUrl: string,
|
logInApiKey: (clientId: string, clientSecret: string) => Promise<AuthResult>;
|
||||||
twoFactorProvider: TwoFactorProviderType, twoFactorToken: string, remember?: boolean) => Promise<AuthResult>;
|
logInTwoFactor: (
|
||||||
logInApiKeyComplete: (clientId: string, clientSecret: string, twoFactorProvider: TwoFactorProviderType,
|
twoFactorProvider: TwoFactorProviderType,
|
||||||
twoFactorToken: string, remember?: boolean) => Promise<AuthResult>;
|
twoFactorToken: string,
|
||||||
logOut: (callback: Function) => void;
|
remember?: boolean
|
||||||
getSupportedTwoFactorProviders: (win: Window) => any[];
|
) => Promise<AuthResult>;
|
||||||
getDefaultTwoFactorProvider: (webAuthnSupported: boolean) => TwoFactorProviderType;
|
logInComplete: (
|
||||||
makePreloginKey: (masterPassword: string, email: string) => Promise<SymmetricCryptoKey>;
|
email: string,
|
||||||
authingWithApiKey: () => boolean;
|
masterPassword: string,
|
||||||
authingWithSso: () => boolean;
|
twoFactorProvider: TwoFactorProviderType,
|
||||||
authingWithPassword: () => boolean;
|
twoFactorToken: string,
|
||||||
|
remember?: boolean,
|
||||||
|
captchaToken?: string
|
||||||
|
) => Promise<AuthResult>;
|
||||||
|
logInSsoComplete: (
|
||||||
|
code: string,
|
||||||
|
codeVerifier: string,
|
||||||
|
redirectUrl: string,
|
||||||
|
twoFactorProvider: TwoFactorProviderType,
|
||||||
|
twoFactorToken: string,
|
||||||
|
remember?: boolean
|
||||||
|
) => Promise<AuthResult>;
|
||||||
|
logInApiKeyComplete: (
|
||||||
|
clientId: string,
|
||||||
|
clientSecret: string,
|
||||||
|
twoFactorProvider: TwoFactorProviderType,
|
||||||
|
twoFactorToken: string,
|
||||||
|
remember?: boolean
|
||||||
|
) => Promise<AuthResult>;
|
||||||
|
logOut: (callback: Function) => void;
|
||||||
|
getSupportedTwoFactorProviders: (win: Window) => any[];
|
||||||
|
getDefaultTwoFactorProvider: (webAuthnSupported: boolean) => TwoFactorProviderType;
|
||||||
|
makePreloginKey: (masterPassword: string, email: string) => Promise<SymmetricCryptoKey>;
|
||||||
|
authingWithApiKey: () => boolean;
|
||||||
|
authingWithSso: () => boolean;
|
||||||
|
authingWithPassword: () => boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
export abstract class BiometricMain {
|
export abstract class BiometricMain {
|
||||||
isError: boolean;
|
isError: boolean;
|
||||||
init: () => Promise<void>;
|
init: () => Promise<void>;
|
||||||
supportsBiometric: () => Promise<boolean>;
|
supportsBiometric: () => Promise<boolean>;
|
||||||
authenticateBiometric: () => Promise<boolean>;
|
authenticateBiometric: () => Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export abstract class BroadcasterService {
|
export abstract class BroadcasterService {
|
||||||
send: (message: any, id?: string) => void;
|
send: (message: any, id?: string) => void;
|
||||||
subscribe: (id: string, messageCallback: (message: any) => any) => void;
|
subscribe: (id: string, messageCallback: (message: any) => any) => void;
|
||||||
unsubscribe: (id: string) => void;
|
unsubscribe: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,57 +1,82 @@
|
||||||
import { CipherType } from '../enums/cipherType';
|
import { CipherType } from "../enums/cipherType";
|
||||||
import { UriMatchType } from '../enums/uriMatchType';
|
import { UriMatchType } from "../enums/uriMatchType";
|
||||||
|
|
||||||
import { CipherData } from '../models/data/cipherData';
|
import { CipherData } from "../models/data/cipherData";
|
||||||
|
|
||||||
import { Cipher } from '../models/domain/cipher';
|
import { Cipher } from "../models/domain/cipher";
|
||||||
import { Field } from '../models/domain/field';
|
import { Field } from "../models/domain/field";
|
||||||
import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey';
|
import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
|
||||||
|
|
||||||
import { CipherView } from '../models/view/cipherView';
|
import { CipherView } from "../models/view/cipherView";
|
||||||
import { FieldView } from '../models/view/fieldView';
|
import { FieldView } from "../models/view/fieldView";
|
||||||
|
|
||||||
export abstract class CipherService {
|
export abstract class CipherService {
|
||||||
clearCache: (userId?: string) => Promise<void>;
|
clearCache: (userId?: string) => Promise<void>;
|
||||||
encrypt: (model: CipherView, key?: SymmetricCryptoKey, originalCipher?: Cipher) => Promise<Cipher>;
|
encrypt: (
|
||||||
encryptFields: (fieldsModel: FieldView[], key: SymmetricCryptoKey) => Promise<Field[]>;
|
model: CipherView,
|
||||||
encryptField: (fieldModel: FieldView, key: SymmetricCryptoKey) => Promise<Field>;
|
key?: SymmetricCryptoKey,
|
||||||
get: (id: string) => Promise<Cipher>;
|
originalCipher?: Cipher
|
||||||
getAll: () => Promise<Cipher[]>;
|
) => Promise<Cipher>;
|
||||||
getAllDecrypted: () => Promise<CipherView[]>;
|
encryptFields: (fieldsModel: FieldView[], key: SymmetricCryptoKey) => Promise<Field[]>;
|
||||||
getAllDecryptedForGrouping: (groupingId: string, folder?: boolean) => Promise<CipherView[]>;
|
encryptField: (fieldModel: FieldView, key: SymmetricCryptoKey) => Promise<Field>;
|
||||||
getAllDecryptedForUrl: (url: string, includeOtherTypes?: CipherType[],
|
get: (id: string) => Promise<Cipher>;
|
||||||
defaultMatch?: UriMatchType) => Promise<CipherView[]>;
|
getAll: () => Promise<Cipher[]>;
|
||||||
getAllFromApiForOrganization: (organizationId: string) => Promise<CipherView[]>;
|
getAllDecrypted: () => Promise<CipherView[]>;
|
||||||
getLastUsedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise<CipherView>;
|
getAllDecryptedForGrouping: (groupingId: string, folder?: boolean) => Promise<CipherView[]>;
|
||||||
getLastLaunchedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise<CipherView>;
|
getAllDecryptedForUrl: (
|
||||||
getNextCipherForUrl: (url: string) => Promise<CipherView>;
|
url: string,
|
||||||
updateLastUsedIndexForUrl: (url: string) => void;
|
includeOtherTypes?: CipherType[],
|
||||||
updateLastUsedDate: (id: string) => Promise<void>;
|
defaultMatch?: UriMatchType
|
||||||
updateLastLaunchedDate: (id: string) => Promise<void>;
|
) => Promise<CipherView[]>;
|
||||||
saveNeverDomain: (domain: string) => Promise<void>;
|
getAllFromApiForOrganization: (organizationId: string) => Promise<CipherView[]>;
|
||||||
saveWithServer: (cipher: Cipher) => Promise<any>;
|
getLastUsedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise<CipherView>;
|
||||||
shareWithServer: (cipher: CipherView, organizationId: string, collectionIds: string[]) => Promise<any>;
|
getLastLaunchedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise<CipherView>;
|
||||||
shareManyWithServer: (ciphers: CipherView[], organizationId: string, collectionIds: string[]) => Promise<any>;
|
getNextCipherForUrl: (url: string) => Promise<CipherView>;
|
||||||
saveAttachmentWithServer: (cipher: Cipher, unencryptedFile: any, admin?: boolean) => Promise<Cipher>;
|
updateLastUsedIndexForUrl: (url: string) => void;
|
||||||
saveAttachmentRawWithServer: (cipher: Cipher, filename: string, data: ArrayBuffer,
|
updateLastUsedDate: (id: string) => Promise<void>;
|
||||||
admin?: boolean) => Promise<Cipher>;
|
updateLastLaunchedDate: (id: string) => Promise<void>;
|
||||||
saveCollectionsWithServer: (cipher: Cipher) => Promise<any>;
|
saveNeverDomain: (domain: string) => Promise<void>;
|
||||||
upsert: (cipher: CipherData | CipherData[]) => Promise<any>;
|
saveWithServer: (cipher: Cipher) => Promise<any>;
|
||||||
replace: (ciphers: { [id: string]: CipherData; }) => Promise<any>;
|
shareWithServer: (
|
||||||
clear: (userId: string) => Promise<any>;
|
cipher: CipherView,
|
||||||
moveManyWithServer: (ids: string[], folderId: string) => Promise<any>;
|
organizationId: string,
|
||||||
delete: (id: string | string[]) => Promise<any>;
|
collectionIds: string[]
|
||||||
deleteWithServer: (id: string) => Promise<any>;
|
) => Promise<any>;
|
||||||
deleteManyWithServer: (ids: string[]) => Promise<any>;
|
shareManyWithServer: (
|
||||||
deleteAttachment: (id: string, attachmentId: string) => Promise<void>;
|
ciphers: CipherView[],
|
||||||
deleteAttachmentWithServer: (id: string, attachmentId: string) => Promise<void>;
|
organizationId: string,
|
||||||
sortCiphersByLastUsed: (a: any, b: any) => number;
|
collectionIds: string[]
|
||||||
sortCiphersByLastUsedThenName: (a: any, b: any) => number;
|
) => Promise<any>;
|
||||||
getLocaleSortingFunction: () => (a: CipherView, b: CipherView) => number;
|
saveAttachmentWithServer: (
|
||||||
softDelete: (id: string | string[]) => Promise<any>;
|
cipher: Cipher,
|
||||||
softDeleteWithServer: (id: string) => Promise<any>;
|
unencryptedFile: any,
|
||||||
softDeleteManyWithServer: (ids: string[]) => Promise<any>;
|
admin?: boolean
|
||||||
restore: (cipher: { id: string, revisionDate: string; } | { id: string, revisionDate: string; }[]) => Promise<any>;
|
) => Promise<Cipher>;
|
||||||
restoreWithServer: (id: string) => Promise<any>;
|
saveAttachmentRawWithServer: (
|
||||||
restoreManyWithServer: (ids: string[]) => Promise<any>;
|
cipher: Cipher,
|
||||||
|
filename: string,
|
||||||
|
data: ArrayBuffer,
|
||||||
|
admin?: boolean
|
||||||
|
) => Promise<Cipher>;
|
||||||
|
saveCollectionsWithServer: (cipher: Cipher) => Promise<any>;
|
||||||
|
upsert: (cipher: CipherData | CipherData[]) => Promise<any>;
|
||||||
|
replace: (ciphers: { [id: string]: CipherData }) => Promise<any>;
|
||||||
|
clear: (userId: string) => Promise<any>;
|
||||||
|
moveManyWithServer: (ids: string[], folderId: string) => Promise<any>;
|
||||||
|
delete: (id: string | string[]) => Promise<any>;
|
||||||
|
deleteWithServer: (id: string) => Promise<any>;
|
||||||
|
deleteManyWithServer: (ids: string[]) => Promise<any>;
|
||||||
|
deleteAttachment: (id: string, attachmentId: string) => Promise<void>;
|
||||||
|
deleteAttachmentWithServer: (id: string, attachmentId: string) => Promise<void>;
|
||||||
|
sortCiphersByLastUsed: (a: any, b: any) => number;
|
||||||
|
sortCiphersByLastUsedThenName: (a: any, b: any) => number;
|
||||||
|
getLocaleSortingFunction: () => (a: CipherView, b: CipherView) => number;
|
||||||
|
softDelete: (id: string | string[]) => Promise<any>;
|
||||||
|
softDeleteWithServer: (id: string) => Promise<any>;
|
||||||
|
softDeleteManyWithServer: (ids: string[]) => Promise<any>;
|
||||||
|
restore: (
|
||||||
|
cipher: { id: string; revisionDate: string } | { id: string; revisionDate: string }[]
|
||||||
|
) => Promise<any>;
|
||||||
|
restoreWithServer: (id: string) => Promise<any>;
|
||||||
|
restoreManyWithServer: (ids: string[]) => Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
import { CollectionData } from '../models/data/collectionData';
|
import { CollectionData } from "../models/data/collectionData";
|
||||||
|
|
||||||
import { Collection } from '../models/domain/collection';
|
import { Collection } from "../models/domain/collection";
|
||||||
import { TreeNode } from '../models/domain/treeNode';
|
import { TreeNode } from "../models/domain/treeNode";
|
||||||
|
|
||||||
import { CollectionView } from '../models/view/collectionView';
|
import { CollectionView } from "../models/view/collectionView";
|
||||||
|
|
||||||
export abstract class CollectionService {
|
export abstract class CollectionService {
|
||||||
clearCache: (userId?: string) => Promise<void>;
|
clearCache: (userId?: string) => Promise<void>;
|
||||||
encrypt: (model: CollectionView) => Promise<Collection>;
|
encrypt: (model: CollectionView) => Promise<Collection>;
|
||||||
decryptMany: (collections: Collection[]) => Promise<CollectionView[]>;
|
decryptMany: (collections: Collection[]) => Promise<CollectionView[]>;
|
||||||
get: (id: string) => Promise<Collection>;
|
get: (id: string) => Promise<Collection>;
|
||||||
getAll: () => Promise<Collection[]>;
|
getAll: () => Promise<Collection[]>;
|
||||||
getAllDecrypted: () => Promise<CollectionView[]>;
|
getAllDecrypted: () => Promise<CollectionView[]>;
|
||||||
getAllNested: (collections?: CollectionView[]) => Promise<TreeNode<CollectionView>[]>;
|
getAllNested: (collections?: CollectionView[]) => Promise<TreeNode<CollectionView>[]>;
|
||||||
getNested: (id: string) => Promise<TreeNode<CollectionView>>;
|
getNested: (id: string) => Promise<TreeNode<CollectionView>>;
|
||||||
upsert: (collection: CollectionData | CollectionData[]) => Promise<any>;
|
upsert: (collection: CollectionData | CollectionData[]) => Promise<any>;
|
||||||
replace: (collections: { [id: string]: CollectionData; }) => Promise<any>;
|
replace: (collections: { [id: string]: CollectionData }) => Promise<any>;
|
||||||
clear: (userId: string) => Promise<any>;
|
clear: (userId: string) => Promise<any>;
|
||||||
delete: (id: string | string[]) => Promise<any>;
|
delete: (id: string | string[]) => Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,63 +1,88 @@
|
||||||
import { EncArrayBuffer } from '../models/domain/encArrayBuffer';
|
import { EncArrayBuffer } from "../models/domain/encArrayBuffer";
|
||||||
import { EncString } from '../models/domain/encString';
|
import { EncString } from "../models/domain/encString";
|
||||||
import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey';
|
import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
|
||||||
|
|
||||||
import { ProfileOrganizationResponse } from '../models/response/profileOrganizationResponse';
|
import { ProfileOrganizationResponse } from "../models/response/profileOrganizationResponse";
|
||||||
import { ProfileProviderOrganizationResponse } from '../models/response/profileProviderOrganizationResponse';
|
import { ProfileProviderOrganizationResponse } from "../models/response/profileProviderOrganizationResponse";
|
||||||
import { ProfileProviderResponse } from '../models/response/profileProviderResponse';
|
import { ProfileProviderResponse } from "../models/response/profileProviderResponse";
|
||||||
|
|
||||||
import { HashPurpose } from '../enums/hashPurpose';
|
import { HashPurpose } from "../enums/hashPurpose";
|
||||||
import { KdfType } from '../enums/kdfType';
|
import { KdfType } from "../enums/kdfType";
|
||||||
import { KeySuffixOptions } from '../enums/keySuffixOptions';
|
import { KeySuffixOptions } from "../enums/keySuffixOptions";
|
||||||
|
|
||||||
export abstract class CryptoService {
|
export abstract class CryptoService {
|
||||||
setKey: (key: SymmetricCryptoKey) => Promise<any>;
|
setKey: (key: SymmetricCryptoKey) => Promise<any>;
|
||||||
setKeyHash: (keyHash: string) => Promise<void>;
|
setKeyHash: (keyHash: string) => Promise<void>;
|
||||||
setEncKey: (encKey: string) => Promise<void>;
|
setEncKey: (encKey: string) => Promise<void>;
|
||||||
setEncPrivateKey: (encPrivateKey: string) => Promise<void>;
|
setEncPrivateKey: (encPrivateKey: string) => Promise<void>;
|
||||||
setOrgKeys: (orgs: ProfileOrganizationResponse[], providerOrgs: ProfileProviderOrganizationResponse[]) => Promise<void>;
|
setOrgKeys: (
|
||||||
setProviderKeys: (orgs: ProfileProviderResponse[]) => Promise<void>;
|
orgs: ProfileOrganizationResponse[],
|
||||||
getKey: (keySuffix?: KeySuffixOptions, userId?: string) => Promise<SymmetricCryptoKey>;
|
providerOrgs: ProfileProviderOrganizationResponse[]
|
||||||
getKeyFromStorage: (keySuffix: KeySuffixOptions) => Promise<SymmetricCryptoKey>;
|
) => Promise<void>;
|
||||||
getKeyHash: () => Promise<string>;
|
setProviderKeys: (orgs: ProfileProviderResponse[]) => Promise<void>;
|
||||||
compareAndUpdateKeyHash: (masterPassword: string, key: SymmetricCryptoKey) => Promise<boolean>;
|
getKey: (keySuffix?: KeySuffixOptions, userId?: string) => Promise<SymmetricCryptoKey>;
|
||||||
getEncKey: (key?: SymmetricCryptoKey) => Promise<SymmetricCryptoKey>;
|
getKeyFromStorage: (keySuffix: KeySuffixOptions) => Promise<SymmetricCryptoKey>;
|
||||||
getPublicKey: () => Promise<ArrayBuffer>;
|
getKeyHash: () => Promise<string>;
|
||||||
getPrivateKey: () => Promise<ArrayBuffer>;
|
compareAndUpdateKeyHash: (masterPassword: string, key: SymmetricCryptoKey) => Promise<boolean>;
|
||||||
getFingerprint: (userId: string, publicKey?: ArrayBuffer) => Promise<string[]>;
|
getEncKey: (key?: SymmetricCryptoKey) => Promise<SymmetricCryptoKey>;
|
||||||
getOrgKeys: () => Promise<Map<string, SymmetricCryptoKey>>;
|
getPublicKey: () => Promise<ArrayBuffer>;
|
||||||
getOrgKey: (orgId: string) => Promise<SymmetricCryptoKey>;
|
getPrivateKey: () => Promise<ArrayBuffer>;
|
||||||
getProviderKey: (providerId: string) => Promise<SymmetricCryptoKey>;
|
getFingerprint: (userId: string, publicKey?: ArrayBuffer) => Promise<string[]>;
|
||||||
hasKey: () => Promise<boolean>;
|
getOrgKeys: () => Promise<Map<string, SymmetricCryptoKey>>;
|
||||||
hasKeyInMemory: (userId?: string) => Promise<boolean>;
|
getOrgKey: (orgId: string) => Promise<SymmetricCryptoKey>;
|
||||||
hasKeyStored: (keySuffix?: KeySuffixOptions, userId?: string) => Promise<boolean>;
|
getProviderKey: (providerId: string) => Promise<SymmetricCryptoKey>;
|
||||||
hasEncKey: () => Promise<boolean>;
|
hasKey: () => Promise<boolean>;
|
||||||
clearKey: (clearSecretStorage?: boolean, userId?: string) => Promise<any>;
|
hasKeyInMemory: (userId?: string) => Promise<boolean>;
|
||||||
clearKeyHash: () => Promise<any>;
|
hasKeyStored: (keySuffix?: KeySuffixOptions, userId?: string) => Promise<boolean>;
|
||||||
clearEncKey: (memoryOnly?: boolean, userId?: string) => Promise<any>;
|
hasEncKey: () => Promise<boolean>;
|
||||||
clearKeyPair: (memoryOnly?: boolean, userId?: string) => Promise<any>;
|
clearKey: (clearSecretStorage?: boolean, userId?: string) => Promise<any>;
|
||||||
clearOrgKeys: (memoryOnly?: boolean, userId?: string) => Promise<any>;
|
clearKeyHash: () => Promise<any>;
|
||||||
clearProviderKeys: (memoryOnly?: boolean) => Promise<any>;
|
clearEncKey: (memoryOnly?: boolean, userId?: string) => Promise<any>;
|
||||||
clearPinProtectedKey: () => Promise<any>;
|
clearKeyPair: (memoryOnly?: boolean, userId?: string) => Promise<any>;
|
||||||
clearKeys: (userId?: string) => Promise<any>;
|
clearOrgKeys: (memoryOnly?: boolean, userId?: string) => Promise<any>;
|
||||||
toggleKey: () => Promise<any>;
|
clearProviderKeys: (memoryOnly?: boolean) => Promise<any>;
|
||||||
makeKey: (password: string, salt: string, kdf: KdfType, kdfIterations: number) => Promise<SymmetricCryptoKey>;
|
clearPinProtectedKey: () => Promise<any>;
|
||||||
makeKeyFromPin: (pin: string, salt: string, kdf: KdfType, kdfIterations: number,
|
clearKeys: (userId?: string) => Promise<any>;
|
||||||
protectedKeyCs?: EncString) => Promise<SymmetricCryptoKey>;
|
toggleKey: () => Promise<any>;
|
||||||
makeShareKey: () => Promise<[EncString, SymmetricCryptoKey]>;
|
makeKey: (
|
||||||
makeKeyPair: (key?: SymmetricCryptoKey) => Promise<[string, EncString]>;
|
password: string,
|
||||||
makePinKey: (pin: string, salt: string, kdf: KdfType, kdfIterations: number) => Promise<SymmetricCryptoKey>;
|
salt: string,
|
||||||
makeSendKey: (keyMaterial: ArrayBuffer) => Promise<SymmetricCryptoKey>;
|
kdf: KdfType,
|
||||||
hashPassword: (password: string, key: SymmetricCryptoKey, hashPurpose?: HashPurpose) => Promise<string>;
|
kdfIterations: number
|
||||||
makeEncKey: (key: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, EncString]>;
|
) => Promise<SymmetricCryptoKey>;
|
||||||
remakeEncKey: (key: SymmetricCryptoKey, encKey?: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, EncString]>;
|
makeKeyFromPin: (
|
||||||
encrypt: (plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey) => Promise<EncString>;
|
pin: string,
|
||||||
encryptToBytes: (plainValue: ArrayBuffer, key?: SymmetricCryptoKey) => Promise<EncArrayBuffer>;
|
salt: string,
|
||||||
rsaEncrypt: (data: ArrayBuffer, publicKey?: ArrayBuffer) => Promise<EncString>;
|
kdf: KdfType,
|
||||||
rsaDecrypt: (encValue: string, privateKeyValue?: ArrayBuffer) => Promise<ArrayBuffer>;
|
kdfIterations: number,
|
||||||
decryptToBytes: (encString: EncString, key?: SymmetricCryptoKey) => Promise<ArrayBuffer>;
|
protectedKeyCs?: EncString
|
||||||
decryptToUtf8: (encString: EncString, key?: SymmetricCryptoKey) => Promise<string>;
|
) => Promise<SymmetricCryptoKey>;
|
||||||
decryptFromBytes: (encBuf: ArrayBuffer, key: SymmetricCryptoKey) => Promise<ArrayBuffer>;
|
makeShareKey: () => Promise<[EncString, SymmetricCryptoKey]>;
|
||||||
randomNumber: (min: number, max: number) => Promise<number>;
|
makeKeyPair: (key?: SymmetricCryptoKey) => Promise<[string, EncString]>;
|
||||||
validateKey: (key: SymmetricCryptoKey) => Promise<boolean>;
|
makePinKey: (
|
||||||
|
pin: string,
|
||||||
|
salt: string,
|
||||||
|
kdf: KdfType,
|
||||||
|
kdfIterations: number
|
||||||
|
) => Promise<SymmetricCryptoKey>;
|
||||||
|
makeSendKey: (keyMaterial: ArrayBuffer) => Promise<SymmetricCryptoKey>;
|
||||||
|
hashPassword: (
|
||||||
|
password: string,
|
||||||
|
key: SymmetricCryptoKey,
|
||||||
|
hashPurpose?: HashPurpose
|
||||||
|
) => Promise<string>;
|
||||||
|
makeEncKey: (key: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, EncString]>;
|
||||||
|
remakeEncKey: (
|
||||||
|
key: SymmetricCryptoKey,
|
||||||
|
encKey?: SymmetricCryptoKey
|
||||||
|
) => Promise<[SymmetricCryptoKey, EncString]>;
|
||||||
|
encrypt: (plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey) => Promise<EncString>;
|
||||||
|
encryptToBytes: (plainValue: ArrayBuffer, key?: SymmetricCryptoKey) => Promise<EncArrayBuffer>;
|
||||||
|
rsaEncrypt: (data: ArrayBuffer, publicKey?: ArrayBuffer) => Promise<EncString>;
|
||||||
|
rsaDecrypt: (encValue: string, privateKeyValue?: ArrayBuffer) => Promise<ArrayBuffer>;
|
||||||
|
decryptToBytes: (encString: EncString, key?: SymmetricCryptoKey) => Promise<ArrayBuffer>;
|
||||||
|
decryptToUtf8: (encString: EncString, key?: SymmetricCryptoKey) => Promise<string>;
|
||||||
|
decryptFromBytes: (encBuf: ArrayBuffer, key: SymmetricCryptoKey) => Promise<ArrayBuffer>;
|
||||||
|
randomNumber: (min: number, max: number) => Promise<number>;
|
||||||
|
validateKey: (key: SymmetricCryptoKey) => Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,62 @@
|
||||||
import { DecryptParameters } from '../models/domain/decryptParameters';
|
import { DecryptParameters } from "../models/domain/decryptParameters";
|
||||||
import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey';
|
import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
|
||||||
|
|
||||||
export abstract class CryptoFunctionService {
|
export abstract class CryptoFunctionService {
|
||||||
pbkdf2: (password: string | ArrayBuffer, salt: string | ArrayBuffer, algorithm: 'sha256' | 'sha512',
|
pbkdf2: (
|
||||||
iterations: number) => Promise<ArrayBuffer>;
|
password: string | ArrayBuffer,
|
||||||
hkdf: (ikm: ArrayBuffer, salt: string | ArrayBuffer, info: string | ArrayBuffer,
|
salt: string | ArrayBuffer,
|
||||||
outputByteSize: number, algorithm: 'sha256' | 'sha512') => Promise<ArrayBuffer>;
|
algorithm: "sha256" | "sha512",
|
||||||
hkdfExpand: (prk: ArrayBuffer, info: string | ArrayBuffer, outputByteSize: number,
|
iterations: number
|
||||||
algorithm: 'sha256' | 'sha512') => Promise<ArrayBuffer>;
|
) => Promise<ArrayBuffer>;
|
||||||
hash: (value: string | ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512' | 'md5') => Promise<ArrayBuffer>;
|
hkdf: (
|
||||||
hmac: (value: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512') => Promise<ArrayBuffer>;
|
ikm: ArrayBuffer,
|
||||||
compare: (a: ArrayBuffer, b: ArrayBuffer) => Promise<boolean>;
|
salt: string | ArrayBuffer,
|
||||||
hmacFast: (value: ArrayBuffer | string, key: ArrayBuffer | string, algorithm: 'sha1' | 'sha256' | 'sha512') =>
|
info: string | ArrayBuffer,
|
||||||
Promise<ArrayBuffer | string>;
|
outputByteSize: number,
|
||||||
compareFast: (a: ArrayBuffer | string, b: ArrayBuffer | string) => Promise<boolean>;
|
algorithm: "sha256" | "sha512"
|
||||||
aesEncrypt: (data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer) => Promise<ArrayBuffer>;
|
) => Promise<ArrayBuffer>;
|
||||||
aesDecryptFastParameters: (data: string, iv: string, mac: string, key: SymmetricCryptoKey) =>
|
hkdfExpand: (
|
||||||
DecryptParameters<ArrayBuffer | string>;
|
prk: ArrayBuffer,
|
||||||
aesDecryptFast: (parameters: DecryptParameters<ArrayBuffer | string>) => Promise<string>;
|
info: string | ArrayBuffer,
|
||||||
aesDecrypt: (data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer) => Promise<ArrayBuffer>;
|
outputByteSize: number,
|
||||||
rsaEncrypt: (data: ArrayBuffer, publicKey: ArrayBuffer, algorithm: 'sha1' | 'sha256') => Promise<ArrayBuffer>;
|
algorithm: "sha256" | "sha512"
|
||||||
rsaDecrypt: (data: ArrayBuffer, privateKey: ArrayBuffer, algorithm: 'sha1' | 'sha256') => Promise<ArrayBuffer>;
|
) => Promise<ArrayBuffer>;
|
||||||
rsaExtractPublicKey: (privateKey: ArrayBuffer) => Promise<ArrayBuffer>;
|
hash: (
|
||||||
rsaGenerateKeyPair: (length: 1024 | 2048 | 4096) => Promise<[ArrayBuffer, ArrayBuffer]>;
|
value: string | ArrayBuffer,
|
||||||
randomBytes: (length: number) => Promise<ArrayBuffer>;
|
algorithm: "sha1" | "sha256" | "sha512" | "md5"
|
||||||
|
) => Promise<ArrayBuffer>;
|
||||||
|
hmac: (
|
||||||
|
value: ArrayBuffer,
|
||||||
|
key: ArrayBuffer,
|
||||||
|
algorithm: "sha1" | "sha256" | "sha512"
|
||||||
|
) => Promise<ArrayBuffer>;
|
||||||
|
compare: (a: ArrayBuffer, b: ArrayBuffer) => Promise<boolean>;
|
||||||
|
hmacFast: (
|
||||||
|
value: ArrayBuffer | string,
|
||||||
|
key: ArrayBuffer | string,
|
||||||
|
algorithm: "sha1" | "sha256" | "sha512"
|
||||||
|
) => Promise<ArrayBuffer | string>;
|
||||||
|
compareFast: (a: ArrayBuffer | string, b: ArrayBuffer | string) => Promise<boolean>;
|
||||||
|
aesEncrypt: (data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer) => Promise<ArrayBuffer>;
|
||||||
|
aesDecryptFastParameters: (
|
||||||
|
data: string,
|
||||||
|
iv: string,
|
||||||
|
mac: string,
|
||||||
|
key: SymmetricCryptoKey
|
||||||
|
) => DecryptParameters<ArrayBuffer | string>;
|
||||||
|
aesDecryptFast: (parameters: DecryptParameters<ArrayBuffer | string>) => Promise<string>;
|
||||||
|
aesDecrypt: (data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer) => Promise<ArrayBuffer>;
|
||||||
|
rsaEncrypt: (
|
||||||
|
data: ArrayBuffer,
|
||||||
|
publicKey: ArrayBuffer,
|
||||||
|
algorithm: "sha1" | "sha256"
|
||||||
|
) => Promise<ArrayBuffer>;
|
||||||
|
rsaDecrypt: (
|
||||||
|
data: ArrayBuffer,
|
||||||
|
privateKey: ArrayBuffer,
|
||||||
|
algorithm: "sha1" | "sha256"
|
||||||
|
) => Promise<ArrayBuffer>;
|
||||||
|
rsaExtractPublicKey: (privateKey: ArrayBuffer) => Promise<ArrayBuffer>;
|
||||||
|
rsaGenerateKeyPair: (length: 1024 | 2048 | 4096) => Promise<[ArrayBuffer, ArrayBuffer]>;
|
||||||
|
randomBytes: (length: number) => Promise<ArrayBuffer>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +1,34 @@
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
export type Urls = {
|
export type Urls = {
|
||||||
base?: string;
|
base?: string;
|
||||||
webVault?: string;
|
webVault?: string;
|
||||||
api?: string;
|
api?: string;
|
||||||
identity?: string;
|
identity?: string;
|
||||||
icons?: string;
|
icons?: string;
|
||||||
notifications?: string;
|
notifications?: string;
|
||||||
events?: string;
|
events?: string;
|
||||||
keyConnector?: string;
|
keyConnector?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PayPalConfig = {
|
export type PayPalConfig = {
|
||||||
businessId?: string;
|
businessId?: string;
|
||||||
buttonAction?: string;
|
buttonAction?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export abstract class EnvironmentService {
|
export abstract class EnvironmentService {
|
||||||
urls: Observable<Urls>;
|
urls: Observable<Urls>;
|
||||||
|
|
||||||
hasBaseUrl: () => boolean;
|
hasBaseUrl: () => boolean;
|
||||||
getNotificationsUrl: () => string;
|
getNotificationsUrl: () => string;
|
||||||
getWebVaultUrl: () => string;
|
getWebVaultUrl: () => string;
|
||||||
getSendUrl: () => string;
|
getSendUrl: () => string;
|
||||||
getIconsUrl: () => string;
|
getIconsUrl: () => string;
|
||||||
getApiUrl: () => string;
|
getApiUrl: () => string;
|
||||||
getIdentityUrl: () => string;
|
getIdentityUrl: () => string;
|
||||||
getEventsUrl: () => string;
|
getEventsUrl: () => string;
|
||||||
getKeyConnectorUrl: () => string;
|
getKeyConnectorUrl: () => string;
|
||||||
setUrlsFromStorage: () => Promise<void>;
|
setUrlsFromStorage: () => Promise<void>;
|
||||||
setUrls: (urls: any, saveSettings?: boolean) => Promise<Urls>;
|
setUrls: (urls: any, saveSettings?: boolean) => Promise<Urls>;
|
||||||
getUrls: () => Urls;
|
getUrls: () => Urls;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { EventType } from '../enums/eventType';
|
import { EventType } from "../enums/eventType";
|
||||||
|
|
||||||
export abstract class EventService {
|
export abstract class EventService {
|
||||||
collect: (eventType: EventType, cipherId?: string, uploadImmediately?: boolean) => Promise<any>;
|
collect: (eventType: EventType, cipherId?: string, uploadImmediately?: boolean) => Promise<any>;
|
||||||
uploadEvents: (userId?: string) => Promise<any>;
|
uploadEvents: (userId?: string) => Promise<any>;
|
||||||
clearEvents: (userId?: string) => Promise<any>;
|
clearEvents: (userId?: string) => Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import { EventView } from '../models/view/eventView';
|
import { EventView } from "../models/view/eventView";
|
||||||
|
|
||||||
export abstract class ExportService {
|
export abstract class ExportService {
|
||||||
getExport: (format?: 'csv' | 'json' | 'encrypted_json') => Promise<string>;
|
getExport: (format?: "csv" | "json" | "encrypted_json") => Promise<string>;
|
||||||
getOrganizationExport: (organizationId: string, format?: 'csv' | 'json' | 'encrypted_json') => Promise<string>;
|
getOrganizationExport: (
|
||||||
getEventExport: (events: EventView[]) => Promise<string>;
|
organizationId: string,
|
||||||
getFileName: (prefix?: string, extension?: string) => string;
|
format?: "csv" | "json" | "encrypted_json"
|
||||||
|
) => Promise<string>;
|
||||||
|
getEventExport: (events: EventView[]) => Promise<string>;
|
||||||
|
getFileName: (prefix?: string, extension?: string) => string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
import { EncArrayBuffer } from '../models/domain/encArrayBuffer';
|
import { EncArrayBuffer } from "../models/domain/encArrayBuffer";
|
||||||
import { EncString } from '../models/domain/encString';
|
import { EncString } from "../models/domain/encString";
|
||||||
import { AttachmentUploadDataResponse } from '../models/response/attachmentUploadDataResponse';
|
import { AttachmentUploadDataResponse } from "../models/response/attachmentUploadDataResponse";
|
||||||
import { SendFileUploadDataResponse } from '../models/response/sendFileUploadDataResponse';
|
import { SendFileUploadDataResponse } from "../models/response/sendFileUploadDataResponse";
|
||||||
|
|
||||||
export abstract class FileUploadService {
|
export abstract class FileUploadService {
|
||||||
uploadSendFile: (uploadData: SendFileUploadDataResponse, fileName: EncString,
|
uploadSendFile: (
|
||||||
encryptedFileData: EncArrayBuffer) => Promise<any>;
|
uploadData: SendFileUploadDataResponse,
|
||||||
uploadCipherAttachment: (admin: boolean, uploadData: AttachmentUploadDataResponse, fileName: EncString,
|
fileName: EncString,
|
||||||
encryptedFileData: EncArrayBuffer) => Promise<any>;
|
encryptedFileData: EncArrayBuffer
|
||||||
|
) => Promise<any>;
|
||||||
|
uploadCipherAttachment: (
|
||||||
|
admin: boolean,
|
||||||
|
uploadData: AttachmentUploadDataResponse,
|
||||||
|
fileName: EncString,
|
||||||
|
encryptedFileData: EncArrayBuffer
|
||||||
|
) => Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
import { FolderData } from '../models/data/folderData';
|
import { FolderData } from "../models/data/folderData";
|
||||||
|
|
||||||
import { Folder } from '../models/domain/folder';
|
import { Folder } from "../models/domain/folder";
|
||||||
import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey';
|
import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
|
||||||
import { TreeNode } from '../models/domain/treeNode';
|
import { TreeNode } from "../models/domain/treeNode";
|
||||||
|
|
||||||
import { FolderView } from '../models/view/folderView';
|
import { FolderView } from "../models/view/folderView";
|
||||||
|
|
||||||
export abstract class FolderService {
|
export abstract class FolderService {
|
||||||
clearCache: (userId?: string) => Promise<void>;
|
clearCache: (userId?: string) => Promise<void>;
|
||||||
encrypt: (model: FolderView, key?: SymmetricCryptoKey) => Promise<Folder>;
|
encrypt: (model: FolderView, key?: SymmetricCryptoKey) => Promise<Folder>;
|
||||||
get: (id: string) => Promise<Folder>;
|
get: (id: string) => Promise<Folder>;
|
||||||
getAll: () => Promise<Folder[]>;
|
getAll: () => Promise<Folder[]>;
|
||||||
getAllDecrypted: () => Promise<FolderView[]>;
|
getAllDecrypted: () => Promise<FolderView[]>;
|
||||||
getAllNested: () => Promise<TreeNode<FolderView>[]>;
|
getAllNested: () => Promise<TreeNode<FolderView>[]>;
|
||||||
getNested: (id: string) => Promise<TreeNode<FolderView>>;
|
getNested: (id: string) => Promise<TreeNode<FolderView>>;
|
||||||
saveWithServer: (folder: Folder) => Promise<any>;
|
saveWithServer: (folder: Folder) => Promise<any>;
|
||||||
upsert: (folder: FolderData | FolderData[]) => Promise<any>;
|
upsert: (folder: FolderData | FolderData[]) => Promise<any>;
|
||||||
replace: (folders: { [id: string]: FolderData; }) => Promise<any>;
|
replace: (folders: { [id: string]: FolderData }) => Promise<any>;
|
||||||
clear: (userId: string) => Promise<any>;
|
clear: (userId: string) => Promise<any>;
|
||||||
delete: (id: string | string[]) => Promise<any>;
|
delete: (id: string | string[]) => Promise<any>;
|
||||||
deleteWithServer: (id: string) => Promise<any>;
|
deleteWithServer: (id: string) => Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
export abstract class I18nService {
|
export abstract class I18nService {
|
||||||
locale: string;
|
locale: string;
|
||||||
supportedTranslationLocales: string[];
|
supportedTranslationLocales: string[];
|
||||||
translationLocale: string;
|
translationLocale: string;
|
||||||
collator: Intl.Collator;
|
collator: Intl.Collator;
|
||||||
localeNames: Map<string, string>;
|
localeNames: Map<string, string>;
|
||||||
t: (id: string, p1?: string, p2?: string, p3?: string) => string;
|
t: (id: string, p1?: string, p2?: string, p3?: string) => string;
|
||||||
translate: (id: string, p1?: string, p2?: string, p3?: string) => string;
|
translate: (id: string, p1?: string, p2?: string, p3?: string) => string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { Importer } from '../importers/importer';
|
import { Importer } from "../importers/importer";
|
||||||
|
|
||||||
export interface ImportOption {
|
export interface ImportOption {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
export abstract class ImportService {
|
export abstract class ImportService {
|
||||||
featuredImportOptions: ImportOption[];
|
featuredImportOptions: ImportOption[];
|
||||||
regularImportOptions: ImportOption[];
|
regularImportOptions: ImportOption[];
|
||||||
getImportOptions: () => ImportOption[];
|
getImportOptions: () => ImportOption[];
|
||||||
import: (importer: Importer, fileContents: string, organizationId?: string) => Promise<Error>;
|
import: (importer: Importer, fileContents: string, organizationId?: string) => Promise<Error>;
|
||||||
getImporter: (format: string, organizationId: string) => Importer;
|
getImporter: (format: string, organizationId: string) => Importer;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { Organization } from '../models/domain/organization';
|
import { Organization } from "../models/domain/organization";
|
||||||
|
|
||||||
export abstract class KeyConnectorService {
|
export abstract class KeyConnectorService {
|
||||||
getAndSetKey: (url?: string) => Promise<void>;
|
getAndSetKey: (url?: string) => Promise<void>;
|
||||||
getManagingOrganization: () => Promise<Organization>;
|
getManagingOrganization: () => Promise<Organization>;
|
||||||
getUsesKeyConnector: () => Promise<boolean>;
|
getUsesKeyConnector: () => Promise<boolean>;
|
||||||
migrateUser: () => Promise<void>;
|
migrateUser: () => Promise<void>;
|
||||||
userNeedsMigration: () => Promise<boolean>;
|
userNeedsMigration: () => Promise<boolean>;
|
||||||
setUsesKeyConnector: (enabled: boolean) => Promise<void>;
|
setUsesKeyConnector: (enabled: boolean) => Promise<void>;
|
||||||
setConvertAccountRequired: (status: boolean) => Promise<void>;
|
setConvertAccountRequired: (status: boolean) => Promise<void>;
|
||||||
getConvertAccountRequired: () => Promise<boolean>;
|
getConvertAccountRequired: () => Promise<boolean>;
|
||||||
removeConvertAccountRequired: () => Promise<void>;
|
removeConvertAccountRequired: () => Promise<void>;
|
||||||
clear: () => Promise<void>;
|
clear: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { LogLevelType } from '../enums/logLevelType';
|
import { LogLevelType } from "../enums/logLevelType";
|
||||||
|
|
||||||
export abstract class LogService {
|
export abstract class LogService {
|
||||||
debug: (message: string) => void;
|
debug: (message: string) => void;
|
||||||
info: (message: string) => void;
|
info: (message: string) => void;
|
||||||
warning: (message: string) => void;
|
warning: (message: string) => void;
|
||||||
error: (message: string) => void;
|
error: (message: string) => void;
|
||||||
write: (level: LogLevelType, message: string) => void;
|
write: (level: LogLevelType, message: string) => void;
|
||||||
time: (label: string) => void;
|
time: (label: string) => void;
|
||||||
timeEnd: (label: string) => [number, number];
|
timeEnd: (label: string) => [number, number];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export abstract class MessagingService {
|
export abstract class MessagingService {
|
||||||
send: (subscriber: string, arg?: any) => void;
|
send: (subscriber: string, arg?: any) => void;
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue