1
0
mirror of https://github.com/bitwarden/web synced 2025-12-06 00:03:28 +00:00

Compare commits

...

45 Commits

Author SHA1 Message Date
Kyle Spearrin
1429cb3f76 New Crowdin updates (#786)
* New translations messages.json (Romanian)

* New translations messages.json (Indonesian)

* New translations messages.json (Swedish)

* New translations messages.json (Turkish)

* New translations messages.json (Ukrainian)

* New translations messages.json (Chinese Simplified)

* New translations messages.json (Chinese Traditional)

* New translations messages.json (Vietnamese)

* New translations messages.json (Portuguese, Brazilian)

* New translations messages.json (Croatian)

* New translations messages.json (Slovenian)

* New translations messages.json (Estonian)

* New translations messages.json (Latvian)

* New translations messages.json (English, United Kingdom)

* New translations messages.json (Esperanto)

* New translations messages.json (Malayalam)

* New translations messages.json (Sinhala)

* New translations messages.json (Norwegian Bokmal)

* New translations messages.json (Serbian (Latin))

* New translations messages.json (Serbian (Cyrillic))

* New translations messages.json (Slovak)

* New translations messages.json (French)

* New translations messages.json (Greek)

* New translations messages.json (Spanish)

* New translations messages.json (Afrikaans)

* New translations messages.json (Belarusian)

* New translations messages.json (Bulgarian)

* New translations messages.json (Catalan)

* New translations messages.json (Czech)

* New translations messages.json (Danish)

* New translations messages.json (German)

* New translations messages.json (Finnish)

* New translations messages.json (Russian)

* New translations messages.json (Hebrew)

* New translations messages.json (Hungarian)

* New translations messages.json (Italian)

* New translations messages.json (Japanese)

* New translations messages.json (Korean)

* New translations messages.json (Dutch)

* New translations messages.json (Polish)

* New translations messages.json (Portuguese)

* New translations messages.json (English, India)
2021-01-19 21:51:17 -05:00
Thomas Rittson
13cbba3e99 Fix typo in selectedPlanInterval (#785) 2021-01-20 09:25:01 +10:00
Chad Scharf
73d24162a0 disable send (#784) 2021-01-19 17:03:34 -05:00
Chad Scharf
4964ebd31e Version bump to 2.18.0 (#783) 2021-01-19 16:18:52 -05:00
Kyle Spearrin
5a8198a878 fixes to plan/pricing page (#776) 2021-01-15 14:46:25 -05:00
Addison Beck
03aa806af6 fixed various Permissions UI issues (#775) 2021-01-14 18:08:26 -05:00
Addison Beck
023bf0474c Showed sales tax amount on Go Premium screen (#774) 2021-01-14 17:53:46 -05:00
Oscar Hinton
2e22ca9216 Remove grantee email from accept-emergency component (#773) 2021-01-14 20:22:49 +01:00
Addison Beck
5a540bba9e Fixed trailing comma lint warnings (#772)
* Fixed trailing comma lint warnings

* Specified options on tslint comma rule
2021-01-13 15:34:06 -05:00
Vincent Salucci
2047a6378b [Policy] Update Personal Ownership checkbox description (#767)
* Initial commit of enabled checkbox description update

* Updated requested changes

* Fixed merge conflict
2021-01-12 17:13:59 -06:00
Addison Beck
dc87510a7a Implemented Custom role and permissions (#750)
* Implemented Custom role and permissions

* converted Permissions interface into a class

* fixed a merge issue

* updated jslib

* code review cleanup for Permissions

* trailing commas
2021-01-12 15:31:22 -05:00
Khánh Hoàng
c3f4c6c03b Fix #759 for Print Code button in Two-step Login (#760) 2021-01-12 12:33:56 -05:00
Matt Gibson
e8b72477c9 Update jslib (#770) 2021-01-08 12:14:20 -06:00
Kyle Spearrin
862874c2ae ui updates for send add/edit component (#768)
* ui updates for send add/edit component

* move messaging.service import
2021-01-07 17:13:25 -05:00
Vincent Salucci
4d2d686078 [Policy] Personal Ownership banner (#764)
* Updated banner position and message

* updated capitalization
2021-01-06 10:01:34 -06:00
Kyle Spearrin
6d458646fa add predefined time frames for delete and expire (#765) 2021-01-05 14:45:23 -05:00
Matt Gibson
a1345488d0 Update jslib (#763) 2021-01-04 11:38:16 -06:00
Kyle Spearrin
c43012a5f2 send improvements and bug fixes (#757)
* send improvements and bug fixes

* update jslib

* update jslib

* update jslib

* update jslib ref

* Hide match uri overflow (#758)

match descriptions are overflowing in german and causing the uri delete
button to overflow off of the cipher view modal

* update jslib

* jslib ref

* update jslib

Co-authored-by: Matt Gibson <mgibson@bitwarden.com>
2021-01-04 10:57:53 -05:00
Kyle Spearrin
577cab24c4 update jslib ref 2021-01-04 10:55:58 -05:00
Vincent Salucci
5c0a77aec8 update jslib (48144a7) (#755) 2020-12-30 20:35:21 -06:00
Matt Gibson
a0904b14ed Hide match uri overflow (#758)
match descriptions are overflowing in german and causing the uri delete
button to overflow off of the cipher view modal
2020-12-30 17:12:01 -06:00
Kyle Spearrin
6774ae0ef3 warning dialog is now handled in base component (#751) 2020-12-22 16:37:50 -05:00
Oscar Hinton
5a76ca4676 Fix linting errors (#749)
* Fix linting errors

* Added back the form promise
2020-12-22 11:28:58 -05:00
Oscar Hinton
3c5a972bc9 Add support for Emergency Access (#707)
* Add support for Emergency Access

* Cleanup & Bugfix

* Apply suggestions from code review

Co-authored-by: Addison Beck <addisonbeck1@gmail.com>

* Cleanup some more imports

* Restrict emergency access invite to premium users

* Restrict editing existing emergency accesses to premium account.

* Handle changes in jslib

* Add some info messages for when you haven't been granted or invited emergency contacts

* Resolve review comments

* Update jslib

Co-authored-by: Addison Beck <addisonbeck1@gmail.com>
2020-12-22 10:57:44 -05:00
Joseph Flinn
54b68ac543 fixing incorrect secret name (#747) 2020-12-21 12:58:51 -08:00
Joseph Flinn
ff378f05fe Moving appveyor to actions (#746)
* initial build testing

* fixing the release event

* fixing typo

* adding windows build

* fixing yaml

* fixing yaml again...

* fixing the windows build
2020-12-21 09:46:36 -08:00
Chad Scharf
f207aa3a9d show caret or icon, not just "blank" space (#745) 2020-12-21 10:59:07 -05:00
Vincent Salucci
7b43dcb6a1 [Policy] Single Org dependency chain (#739)
* Initial commit of Single Org downstream policy checks

* Moved comments
2020-12-17 14:20:45 -06:00
Matt Gibson
c487cf3284 Add message for missing event type (#740)
* Add message for missing event type

* update jslib reference
2020-12-16 18:29:19 -06:00
Chad Scharf
c2e1d325f2 update jslib and fix webPlatformUtils (#741) 2020-12-16 16:40:10 -05:00
Mithilesh Zavar
f090e8febf Password History Overflow (#743) 2020-12-16 15:47:37 -05:00
Wyatt Childers
087c84bcfb Only show carats for items with children (#410)
* Only show carats for folders with children

* Only show carats for collections with children
2020-12-15 16:32:49 -05:00
Mithilesh Zavar
a457c83242 Attachments with long file names go beyond the window. #695 (#702) 2020-12-15 16:03:17 -05:00
Matt Gibson
bcd8963e8b Add totp copy to clipboard button to cipher view (#737)
* Add totp copy to clipboard button to cipher view

* Align totp copy privs with cipher view

* Enforce TOTP as premium feature

* Update jslib reference
2020-12-15 10:25:52 -06:00
Matt Gibson
1464e0fbe8 Add ConsoleLogService dependency from jslib (#735)
* Pre-emptively add new jslib dependency

* Add consoleLogService dependency definition

* Update jslib

* PR Review

Co-authored-by: Matt Gibson <mdgibson@Matts-MBP.lan>
2020-12-14 12:27:32 -06:00
Kyle Spearrin
ec2b048289 enable send 2020-12-11 16:43:28 -05:00
Joshua McCauley
04811c934f Updated readme to note how to run the app against production (#706)
* Noted upper limit of Node.js verion support for the application: the SCSS dependency v4.13.1 only supports Node.js up to v13.9.0.
Added note for npm commands for running the application against local APIs versus production. The correct npm command for running against production was found here https://github.com/bitwarden/web/issues/666.
Added more lines to the services.module.ts example to better reflect the actual file.

* Added CORS common issue and solution to README.md

* Changed Node.js version notes for real this time.
2020-12-10 12:08:31 -05:00
Vincent Salucci
f84ee30b9d Fixed casing for error message (#734) 2020-12-09 13:30:55 -06:00
Vincent Salucci
218caa28b0 [Policy] Personal Ownership (#722)
* Initial commit of personal ownership policy

* Added event handling for modifying policies

* I didn't save the merge conflict fix...

* Removed unused import

* Updated jslib (dcbd09e -> 2d62e10)
2020-12-08 13:24:59 -06:00
Matt Gibson
a8af807650 Add help text for jslib's mac importer (#731)
Co-authored-by: Matt Gibson <mdgibson@Matts-MBP.lan>
2020-12-08 09:16:50 -06:00
vachan-maker
826170507e Updated Firefox Import Instructions (#728) 2020-12-07 16:51:53 -05:00
Kyle Spearrin
c37979e48d update getImporter signature pass org id (#730) 2020-12-07 13:02:56 -05:00
Addison Beck
7ebb046cd8 updated jslib (#727) 2020-12-04 12:39:41 -05:00
Addison Beck
7c4d0a15dd Implemented tax collection for subscriptions (#723)
* Implemented tax collection for subscriptions

* Cleanup for Sales Tax

* Code review fixes for Tax Rate implementation

* Code review fixes for Tax Rate implementation
2020-12-04 12:05:44 -05:00
Kyle Spearrin
512b9e0a92 encrypted json export option for user and orgs (#726)
* encrypted json export option for user and orgs

* move org id to base export component
2020-12-04 09:58:26 -05:00
131 changed files with 18998 additions and 1816 deletions

View File

@@ -13,3 +13,6 @@ insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 4
[*.{ts}]
quote_type = single

168
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,168 @@
name: build
on:
push:
branches-ignore:
- 'l10n_master'
- 'gh-pages'
release:
types:
- published
jobs:
cloc:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v2
- name: Set up cloc
run: |
sudo apt update
sudo apt -y install cloc
- name: Print lines of code
run: cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git
ubuntu:
runs-on: ubuntu-latest
steps:
- name: Set up Node
uses: actions/setup-node@v1
with:
node-version: '10.x'
- name: Print environment
run: |
whoami
node --version
npm --version
gulp --version
docker --version
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
env:
GITHUB_REF: ${{ github.ref }}
GITHUB_EVENT: ${{ github.event_name }}
- name: Log into docker
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
- name: Setup Docker Trust
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: |
mkdir -p ~/.docker/trust/private
echo "${{ secrets.DOCKER_DELEGATION_KEY }}" > ~/.docker/trust/private/$DOCKER_DELEGATION_KEY_ID.key
echo "${{ secrets.DOCKER_REPO_WEB_KEY }}" > ~/.docker/trust/private/$DOCKER_WEB_KEY_ID.key
env:
DOCKER_DELEGATION_KEY_ID: "5702b22123e058cbd96a7a43000cb981ae98ef3f2f4aa34138ab3dc1d011e446"
DOCKER_WEB_KEY_ID: "0f88641697187f42a31b584897cd4edfe80360a5209122d9e7f71af17a6422e4"
- name: Checkout repo
uses: actions/checkout@v2
- name: Restore
run: dotnet tool restore
- name: Build
run: |
chmod +x ./build.sh
./build.sh
- name: Tag dev
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./build.sh tag dev
- name: Tag beta
if: github.event_name == 'release'
run: ./build.sh tag beta
- name: Tag version
if: github.event_name == 'release'
run: ./build.sh tag $($env:RELEASE_TAG_NAME.trimStart('v'))
shell: pwsh
env:
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
- name: List docker images
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: docker images
- name: Push dev images
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./build.sh push dev
env:
DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ secrets.DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE }}
- name: Push beta images
if: github.event_name == 'release'
run: ./build.sh push beta
env:
DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ secrets.DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE }}
- name: Push latest images
if: github.event_name == 'release'
run: ./build.sh push latest
env:
DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ secrets.DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE }}
- name: Push version images
if: github.event_name == 'release'
run: ./build.sh push $($env:RELEASE_TAG_NAME.trimStart('v'))
shell: pwsh
env:
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ secrets.DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE }}
- name: Log out of docker
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: docker logout
windows:
runs-on: windows-latest
steps:
- name: Set up NuGet
uses: nuget/setup-nuget@v1
with:
nuget-version: 'latest'
- name: Set up MSBuild
uses: microsoft/setup-msbuild@v1
- name: Set up Node
uses: actions/setup-node@v1
with:
node-version: '10.x'
- name: Print environment
run: |
nuget help
msbuild -version
dotnet --info
node --version
npm --version
Write-Output "GitHub ref: $env:GITHUB_REF"
Write-Output "GitHub event: $env:GITHUB_EVENT"
shell: pwsh
env:
GITHUB_REF: ${{ github.ref }}
GITHUB_EVENT: ${{ github.event_name }}
- name: Checkout repo
uses: actions/checkout@v2
- name: npm install
run: npm install
- name: npm build
run: npm run build:prod

View File

@@ -27,6 +27,8 @@
### Run the app
For local development, run the app with:
```
npm install
npm run build:watch
@@ -39,6 +41,7 @@ await apiService.setUrls({
base: isDev ? null : window.location.origin,
api: isDev ? 'http://mylocalapi' : null,
identity: isDev ? 'http://mylocalidentity' : null,
events: isDev ? 'http://mylocalevents' : null,
});
```
@@ -49,9 +52,31 @@ await apiService.setUrls({
base: null,
api: 'https://api.bitwarden.com',
identity: 'https://identity.bitwarden.com',
events: 'https://events.bitwarden.com',
});
```
And note to run the app with:
```
npm install
npm run build:prod:watch
```
## Common Issues:
### CORS
If you run the frontend and receive a notification after attempting to login that says:
```
An error has occurred.
NetworkError when attempting to fetch resource.
```
And in the console:
```
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://api.bitwarden.com/accounts/prelogin. (Reason: CORS header Access-Control-Allow-Origin missing).
```
This means that you are having a CORS header issue. This can be mitigated by using a CORS header changing extension in your browser such as [this one.](https://mybrowseraddon.com/access-control-allow-origin.html)
## Contribute

2
jslib

Submodule jslib updated: abb54f0073...6ac6df75d7

5
package-lock.json generated
View File

@@ -1552,6 +1552,11 @@
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
"dev": true
},
"browser-process-hrtime": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz",
"integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow=="
},
"browserify-aes": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "bitwarden-web",
"version": "2.17.1",
"version": "2.18.0",
"license": "GPL-3.0",
"repository": "https://github.com/bitwarden/web",
"scripts": {
@@ -80,6 +80,7 @@
"big-integer": "1.6.36",
"bootstrap": "4.3.1",
"braintree-web-drop-in": "1.13.0",
"browser-process-hrtime": "1.0.0",
"core-js": "2.6.2",
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git#410a9186cc34663c4913b17d6528067cd3331f1d",
"font-awesome": "4.7.0",

View File

@@ -0,0 +1,34 @@
<div class="mt-5 d-flex justify-content-center" *ngIf="loading">
<div>
<img src="../../images/logo-dark@2x.png" class="mb-4 logo" alt="Bitwarden">
<p class="text-center">
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'loading' | i18n}}</span>
</p>
</div>
</div>
<div class="container" *ngIf="!loading && !authed">
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{'emergencyAccess' | i18n}}</p>
<div class="card d-block">
<div class="card-body">
<p class="text-center">
{{name}}
</p>
<p>{{'acceptEmergencyAccess' | i18n}}</p>
<hr>
<div class="d-flex">
<a routerLink="/" [queryParams]="{email: email}" class="btn btn-primary btn-block">
{{'logIn' | i18n}}
</a>
<a routerLink="/register" [queryParams]="{email: email}"
class="btn btn-primary btn-block ml-2 mt-0">
{{'createAccount' | i18n}}
</a>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,93 @@
import {
Component,
OnInit,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import {
Toast,
ToasterService,
} from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { StateService } from 'jslib/abstractions/state.service';
import { UserService } from 'jslib/abstractions/user.service';
import { EmergencyAccessAcceptRequest } from 'jslib/models/request/emergencyAccessAcceptRequest';
@Component({
selector: 'app-accept-emergency',
templateUrl: 'accept-emergency.component.html',
})
export class AcceptEmergencyComponent implements OnInit {
loading = true;
authed = false;
name: string;
email: string;
actionPromise: Promise<any>;
constructor(private router: Router, private toasterService: ToasterService,
private i18nService: I18nService, private route: ActivatedRoute,
private apiService: ApiService, private userService: UserService,
private stateService: StateService) { }
ngOnInit() {
let fired = false;
this.route.queryParams.subscribe(async (qParams) => {
if (fired) {
return;
}
fired = true;
await this.stateService.remove('emergencyInvitation');
let error = qParams.id == null || qParams.name == null || qParams.email == null || qParams.token == null;
let errorMessage: string = null;
if (!error) {
this.authed = await this.userService.isAuthenticated();
if (this.authed) {
const request = new EmergencyAccessAcceptRequest();
request.token = qParams.token;
try {
this.actionPromise = this.apiService.postEmergencyAccessAccept(qParams.id, request);
await this.actionPromise;
const toast: Toast = {
type: 'success',
title: this.i18nService.t('inviteAccepted'),
body: this.i18nService.t('emergencyInviteAcceptedDesc'),
timeout: 10000,
};
this.toasterService.popAsync(toast);
this.router.navigate(['/vault']);
} catch (e) {
error = true;
errorMessage = e.message;
}
} else {
await this.stateService.save('emergencyInvitation', qParams);
this.email = qParams.email;
this.name = qParams.name;
if (this.name != null) {
// Fix URL encoding of space issue with Angular
this.name = this.name.replace(/\+/g, ' ');
}
}
}
if (error) {
const toast: Toast = {
type: 'error',
title: null,
body: errorMessage != null ? this.i18nService.t('emergencyInviteAcceptFailedShort', errorMessage) :
this.i18nService.t('emergencyInviteAcceptFailed'),
timeout: 10000,
};
this.toasterService.popAsync(toast);
this.router.navigate(['/']);
}
this.loading = false;
});
}
}

View File

@@ -52,9 +52,12 @@ export class LoginComponent extends BaseLoginComponent {
}
async goAfterLogIn() {
const invite = await this.stateService.get<any>('orgInvitation');
if (invite != null) {
this.router.navigate(['accept-organization'], { queryParams: invite });
const orgInvite = await this.stateService.get<any>('orgInvitation');
const emergencyInvite = await this.stateService.get<any>('emergencyInvitation');
if (orgInvite != null) {
this.router.navigate(['accept-organization'], { queryParams: orgInvite });
} else if (emergencyInvite != null) {
this.router.navigate(['accept-emergency'], { queryParams: emergencyInvite });
} else {
const loginRedirect = await this.stateService.get<any>('loginRedirect');
if (loginRedirect != null) {

View File

@@ -54,7 +54,7 @@ export class SsoComponent extends BaseSsoComponent {
async submit() {
await this.storageService.save(IdentifierStorageKey, this.identifier);
if (this.clientId === 'browser') {
document.cookie = `ssoHandOffMessage=${this.i18nService.t('ssoHandOff')};SameSite=strict`
document.cookie = `ssoHandOffMessage=${this.i18nService.t('ssoHandOff')};SameSite=strict`;
}
super.submit();
}

View File

@@ -8,6 +8,7 @@ import { FrontendLayoutComponent } from './layouts/frontend-layout.component';
import { OrganizationLayoutComponent } from './layouts/organization-layout.component';
import { UserLayoutComponent } from './layouts/user-layout.component';
import { AcceptEmergencyComponent } from './accounts/accept-emergency.component';
import { AcceptOrganizationComponent } from './accounts/accept-organization.component';
import { HintComponent } from './accounts/hint.component';
import { LockComponent } from './accounts/lock.component';
@@ -90,7 +91,10 @@ import { UnauthGuardService } from './services/unauth-guard.service';
import { AuthGuardService } from 'jslib/angular/services/auth-guard.service';
import { OrganizationUserType } from 'jslib/enums/organizationUserType';
import { Permissions } from 'jslib/enums/permissions';
import { EmergencyAccessViewComponent } from './settings/emergency-access-view.component';
import { EmergencyAccessComponent } from './settings/emergency-access.component';
const routes: Routes = [
{
@@ -125,6 +129,11 @@ const routes: Routes = [
component: AcceptOrganizationComponent,
data: { titleId: 'joinOrganization' },
},
{
path: 'accept-emergency',
component: AcceptEmergencyComponent,
data: { titleId: 'acceptEmergency' },
},
{ path: 'recover', pathMatch: 'full', redirectTo: 'recover-2fa' },
{
path: 'recover-2fa',
@@ -144,11 +153,11 @@ const routes: Routes = [
canActivate: [UnauthGuardService],
data: { titleId: 'deleteAccount' },
},
{
/*{
path: 'send/:sendId/:key',
component: AccessComponent,
data: { title: 'Bitwarden Send' },
},
},*/
],
},
{
@@ -157,7 +166,7 @@ const routes: Routes = [
canActivate: [AuthGuardService],
children: [
{ path: 'vault', component: VaultComponent, data: { titleId: 'myVault' } },
// { path: 'sends', component: SendComponent, data: { title: 'Send' } },
{ path: 'sends', component: SendComponent, data: { title: 'Send' } },
{
path: 'settings',
component: SettingsComponent,
@@ -180,6 +189,21 @@ const routes: Routes = [
component: CreateOrganizationComponent,
data: { titleId: 'newOrganization' },
},
{
path: 'emergency-access',
children: [
{
path: '',
component: EmergencyAccessComponent,
data: { titleId: 'emergencyAccess'},
},
{
path: ':id',
component: EmergencyAccessViewComponent,
data: { titleId: 'emergencyAccess'},
},
],
},
],
},
{
@@ -236,35 +260,75 @@ const routes: Routes = [
path: 'tools',
component: OrgToolsComponent,
canActivate: [OrganizationTypeGuardService],
data: { allowedTypes: [OrganizationUserType.Owner, OrganizationUserType.Admin] },
data: { permissions: [Permissions.AccessImportExport, Permissions.AccessReports] },
children: [
{ path: '', pathMatch: 'full', redirectTo: 'import' },
{ path: 'import', component: OrgImportComponent, data: { titleId: 'importData' } },
{ path: 'export', component: OrgExportComponent, data: { titleId: 'exportVault' } },
{
path: '',
pathMatch: 'full',
redirectTo: 'import',
},
{
path: 'import',
component: OrgImportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: 'importData',
permissions: [Permissions.AccessImportExport],
},
},
{
path: 'export',
component: OrgExportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: 'exportVault',
permissions: [Permissions.AccessImportExport],
},
},
{
path: 'exposed-passwords-report',
component: OrgExposedPasswordsReportComponent,
data: { titleId: 'exposedPasswordsReport' },
canActivate: [OrganizationTypeGuardService],
data: {
titleId: 'exposedPasswordsReport',
permissions: [Permissions.AccessReports],
},
},
{
path: 'inactive-two-factor-report',
component: OrgInactiveTwoFactorReportComponent,
data: { titleId: 'inactive2faReport' },
canActivate: [OrganizationTypeGuardService],
data: {
titleId: 'inactive2faReport',
permissions: [Permissions.AccessReports],
},
},
{
path: 'reused-passwords-report',
component: OrgReusedPasswordsReportComponent,
data: { titleId: 'reusedPasswordsReport' },
canActivate: [OrganizationTypeGuardService],
data: {
titleId: 'reusedPasswordsReport',
permissions: [Permissions.AccessReports],
},
},
{
path: 'unsecured-websites-report',
component: OrgUnsecuredWebsitesReportComponent,
data: { titleId: 'unsecuredWebsitesReport' },
canActivate: [OrganizationTypeGuardService],
data: {
titleId: 'unsecuredWebsitesReport',
permissions: [Permissions.AccessReports],
},
},
{
path: 'weak-passwords-report',
component: OrgWeakPasswordsReportComponent,
data: { titleId: 'weakPasswordsReport' },
canActivate: [OrganizationTypeGuardService],
data: {
titleId: 'weakPasswordsReport',
permissions: [Permissions.AccessReports],
},
},
],
},
@@ -273,26 +337,73 @@ const routes: Routes = [
component: OrgManageComponent,
canActivate: [OrganizationTypeGuardService],
data: {
allowedTypes: [
OrganizationUserType.Owner,
OrganizationUserType.Admin,
OrganizationUserType.Manager,
permissions: [
Permissions.ManageAssignedCollections,
Permissions.ManageAllCollections,
Permissions.AccessEventLogs,
Permissions.ManageGroups,
Permissions.ManageUsers,
Permissions.ManagePolicies,
],
},
children: [
{ path: '', pathMatch: 'full', redirectTo: 'people' },
{ path: 'collections', component: OrgManageCollectionsComponent, data: { titleId: 'collections' } },
{ path: 'events', component: OrgEventsComponent, data: { titleId: 'eventLogs' } },
{ path: 'groups', component: OrgGroupsComponent, data: { titleId: 'groups' } },
{ path: 'people', component: OrgPeopleComponent, data: { titleId: 'people' } },
{ path: 'policies', component: OrgPoliciesComponent, data: { titleId: 'policies' } },
{
path: '',
pathMatch: 'full',
redirectTo: 'people',
},
{
path: 'collections',
component: OrgManageCollectionsComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: 'collections',
permissions: [Permissions.ManageAssignedCollections, Permissions.ManageAllCollections],
},
},
{
path: 'events',
component: OrgEventsComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: 'eventLogs',
permissions: [Permissions.AccessEventLogs],
},
},
{
path: 'groups',
component: OrgGroupsComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: 'groups',
permissions: [Permissions.ManageGroups],
},
},
{
path: 'people',
component: OrgPeopleComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: 'people',
permissions: [Permissions.ManageUsers],
},
},
{
path: 'policies',
component: OrgPoliciesComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: 'policies',
permissions: [Permissions.ManagePolicies],
},
},
],
},
{
path: 'settings',
component: OrgSettingsComponent,
canActivate: [OrganizationTypeGuardService],
data: { allowedTypes: [OrganizationUserType.Owner] },
data: { permissions: [Permissions.ManageOrganization] },
children: [
{ path: '', pathMatch: 'full', redirectTo: 'account' },
{ path: 'account', component: OrgAccountComponent, data: { titleId: 'myOrganization' } },
@@ -317,6 +428,7 @@ const routes: Routes = [
@NgModule({
imports: [RouterModule.forRoot(routes, {
useHash: true,
paramsInheritanceStrategy: 'always',
/*enableTracing: true,*/
})],
exports: [RouterModule],

View File

@@ -27,6 +27,7 @@ import { NavbarComponent } from './layouts/navbar.component';
import { OrganizationLayoutComponent } from './layouts/organization-layout.component';
import { UserLayoutComponent } from './layouts/user-layout.component';
import { AcceptEmergencyComponent } from './accounts/accept-emergency.component';
import { AcceptOrganizationComponent } from './accounts/accept-organization.component';
import { HintComponent } from './accounts/hint.component';
import { LockComponent } from './accounts/lock.component';
@@ -112,6 +113,12 @@ import { CreateOrganizationComponent } from './settings/create-organization.comp
import { DeauthorizeSessionsComponent } from './settings/deauthorize-sessions.component';
import { DeleteAccountComponent } from './settings/delete-account.component';
import { DomainRulesComponent } from './settings/domain-rules.component';
import { EmergencyAccessAddEditComponent } from './settings/emergency-access-add-edit.component';
import { EmergencyAccessComponent } from './settings/emergency-access.component';
import { EmergencyAccessConfirmComponent } from './settings/emergency-access-confirm.component';
import { EmergencyAccessTakeoverComponent } from './settings/emergency-access-takeover.component';
import { EmergencyAccessViewComponent } from './settings/emergency-access-view.component';
import { EmergencyAddEditComponent } from './settings/emergency-add-edit.component';
import { LinkSsoComponent } from './settings/link-sso.component';
import { OptionsComponent } from './settings/options.component';
import { OrganizationPlansComponent } from './settings/organization-plans.component';
@@ -258,6 +265,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
],
declarations: [
A11yTitleDirective,
AcceptEmergencyComponent,
AccessComponent,
AcceptOrganizationComponent,
AccountComponent,
@@ -295,6 +303,12 @@ registerLocaleData(localeZhTw, 'zh-TW');
DeleteOrganizationComponent,
DomainRulesComponent,
DownloadLicenseComponent,
EmergencyAccessAddEditComponent,
EmergencyAccessComponent,
EmergencyAccessConfirmComponent,
EmergencyAccessTakeoverComponent,
EmergencyAccessViewComponent,
EmergencyAddEditComponent,
ExportComponent,
ExposedPasswordsReportComponent,
FallbackSrcDirective,
@@ -409,6 +423,10 @@ registerLocaleData(localeZhTw, 'zh-TW');
DeauthorizeSessionsComponent,
DeleteAccountComponent,
DeleteOrganizationComponent,
EmergencyAccessAddEditComponent,
EmergencyAccessConfirmComponent,
EmergencyAccessTakeoverComponent,
EmergencyAddEditComponent,
FolderAddEditComponent,
ModalComponent,
OrgAddEditComponent,

View File

@@ -8,11 +8,9 @@
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/vault">{{'myVault' | i18n}}</a>
</li>
<!--
<li class="nav-item" routerLinkActive="active">
<!--<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/sends">Send</a>
</li>
-->
</li>-->
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/tools">{{'tools' | i18n}}</a>
</li>

View File

@@ -15,21 +15,21 @@
</div>
</div>
</div>
<ul class="nav nav-tabs" *ngIf="organization.isManager">
<ul class="nav nav-tabs" *ngIf="showMenuBar">
<li class="nav-item">
<a class="nav-link" routerLink="vault" routerLinkActive="active">
<i class="fa fa-lock" aria-hidden="true"></i>
{{'vault' | i18n}}
</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="manage" routerLinkActive="active">
<li class="nav-item" *ngIf="showManageTab">
<a class="nav-link" [routerLink]="manageRoute" routerLinkActive="active">
<i class="fa fa-sliders" aria-hidden="true"></i>
{{'manage' | i18n}}
</a>
</li>
<li class="nav-item" *ngIf="organization.isAdmin">
<a class="nav-link" routerLink="tools" routerLinkActive="active">
<li class="nav-item" *ngIf="showToolsTab">
<a class="nav-link" [routerLink]="toolsRoute" routerLinkActive="active">
<i class="fa fa-wrench" aria-hidden="true"></i>
{{'tools' | i18n}}
</a>
@@ -43,10 +43,10 @@
</ul>
</div>
<div class="ml-auto d-flex align-items-center">
<button class="btn btn-primary" (click)="goToEnterprisePortal()" #enterpriseBtn
[appApiAction]="enterpriseTokenPromise" *ngIf="organization.useBusinessPortal">
<i class="fa fa-bank fa-fw" [hidden]="enterpriseBtn.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-fw" [hidden]="!enterpriseBtn.loading" title="{{'loading' | i18n}}"
<button class="btn btn-primary" (click)="goToBusinessPortal()" #businessBtn
[appApiAction]="businessTokenPromise" *ngIf="showBusinessPortalButton">
<i class="fa fa-bank fa-fw" [hidden]="businessBtn.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-fw" [hidden]="!businessBtn.loading" title="{{'loading' | i18n}}"
aria-hidden="true"></i>
{{'businessPortal' | i18n}} →
</button>

View File

@@ -24,9 +24,9 @@ const BroadcasterSubscriptionId = 'OrganizationLayoutComponent';
})
export class OrganizationLayoutComponent implements OnInit, OnDestroy {
organization: Organization;
enterpriseTokenPromise: Promise<any>;
businessTokenPromise: Promise<any>;
private organizationId: string;
private enterpriseUrl: string;
private businessUrl: string;
constructor(private route: ActivatedRoute, private userService: UserService,
private broadcasterService: BroadcasterService, private ngZone: NgZone,
@@ -34,11 +34,11 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
private environmentService: EnvironmentService) { }
ngOnInit() {
this.enterpriseUrl = 'https://portal.bitwarden.com';
this.businessUrl = 'https://portal.bitwarden.com';
if (this.environmentService.enterpriseUrl != null) {
this.enterpriseUrl = this.environmentService.enterpriseUrl;
this.businessUrl = this.environmentService.enterpriseUrl;
} else if (this.environmentService.baseUrl != null) {
this.enterpriseUrl = this.environmentService.baseUrl + '/portal';
this.businessUrl = this.environmentService.baseUrl + '/portal';
}
document.body.classList.remove('layout_frontend');
@@ -65,19 +65,68 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
this.organization = await this.userService.getOrganization(this.organizationId);
}
async goToEnterprisePortal() {
if (this.enterpriseTokenPromise != null) {
async goToBusinessPortal() {
if (this.businessTokenPromise != null) {
return;
}
try {
this.enterpriseTokenPromise = this.apiService.getEnterprisePortalSignInToken();
const token = await this.enterpriseTokenPromise;
this.businessTokenPromise = this.apiService.getEnterprisePortalSignInToken();
const token = await this.businessTokenPromise;
if (token != null) {
const userId = await this.userService.getUserId();
this.platformUtilsService.launchUri(this.enterpriseUrl + '/login?userId=' + userId +
this.platformUtilsService.launchUri(this.businessUrl + '/login?userId=' + userId +
'&token=' + (window as any).encodeURIComponent(token) + '&organizationId=' + this.organization.id);
}
} catch { }
this.enterpriseTokenPromise = null;
this.businessTokenPromise = null;
}
get showMenuBar() {
return this.showManageTab || this.showToolsTab || this.organization.isOwner;
}
get showManageTab(): boolean {
return this.organization.canManageUsers ||
this.organization.canManageAssignedCollections ||
this.organization.canManageAllCollections ||
this.organization.canManageGroups ||
this.organization.canManagePolicies ||
this.organization.canAccessEventLogs;
}
get showToolsTab(): boolean {
return this.organization.canAccessImportExport || this.organization.canAccessReports;
}
get showBusinessPortalButton(): boolean {
return this.organization.useBusinessPortal && this.organization.canAccessBusinessPortal;
}
get toolsRoute(): string {
return this.organization.canAccessImportExport ?
'tools/import' :
'tools/exposed-passwords-report';
}
get manageRoute(): string {
let route: string;
switch (true) {
case this.organization.canManageUsers:
route = 'manage/people';
break;
case this.organization.canManageAssignedCollections || this.organization.canManageAllCollections:
route = 'manage/collections';
break;
case this.organization.canManageGroups:
route = 'manage/groups';
break;
case this.organization.canManagePolicies:
route = 'manage/policies';
break;
case this.organization.canAccessEventLogs:
route = 'manage/events';
break;
}
return route;
}
}

View File

@@ -72,7 +72,7 @@ export class CollectionsComponent implements OnInit {
async load() {
const organization = await this.userService.getOrganization(this.organizationId);
let response: ListResponse<CollectionResponse>;
if (organization.isAdmin) {
if (organization.canManageAllCollections) {
response = await this.apiService.getCollections(this.organizationId);
} else {
response = await this.apiService.getUserCollections();

View File

@@ -86,6 +86,7 @@
<span *ngIf="u.type === organizationUserType.Admin">{{'admin' | i18n}}</span>
<span *ngIf="u.type === organizationUserType.Manager">{{'manager' | i18n}}</span>
<span *ngIf="u.type === organizationUserType.User">{{'user' | i18n}}</span>
<span *ngIf="u.type === organizationUserType.Custom">{{'custom' | i18n}}</span>
</td>
<td class="text-center" *ngIf="entity === 'collection'">
<input type="checkbox" [(ngModel)]="u.hidePasswords"

View File

@@ -5,22 +5,23 @@
<div class="card-header">{{'manage' | i18n}}</div>
<div class="list-group list-group-flush">
<a routerLink="people" class="list-group-item" routerLinkActive="active"
*ngIf="organization.isAdmin">
*ngIf="organization.canManageUsers">
{{'people' | i18n}}
</a>
<a routerLink="collections" class="list-group-item" routerLinkActive="active">
<a routerLink="collections" class="list-group-item" routerLinkActive="active"
*ngIf="organization.canManageAssignedCollections || organization.canManageAllCollections">
{{'collections' | i18n}}
</a>
<a routerLink="groups" class="list-group-item" routerLinkActive="active"
*ngIf="organization.isAdmin && accessGroups">
*ngIf="organization.canManageGroups && accessGroups">
{{'groups' | i18n}}
</a>
<a routerLink="policies" class="list-group-item" routerLinkActive="active"
*ngIf="organization.isAdmin && accessPolicies">
*ngIf="organization.canManagePolicies && accessPolicies">
{{'policies' | i18n}}
</a>
<a routerLink="events" class="list-group-item" routerLinkActive="active"
*ngIf="organization.isAdmin && accessEvents">
*ngIf="organization.canAccessEventLogs && accessEvents">
{{'eventLogs' | i18n}}
</a>
</div>

View File

@@ -69,6 +69,7 @@
<span *ngIf="u.type === organizationUserType.Admin">{{'admin' | i18n}}</span>
<span *ngIf="u.type === organizationUserType.Manager">{{'manager' | i18n}}</span>
<span *ngIf="u.type === organizationUserType.User">{{'user' | i18n}}</span>
<span *ngIf="u.type === organizationUserType.Custom">{{'custom' | i18n}}</span>
</td>
<td class="table-list-options">
<div class="dropdown" appListDropdown>

View File

@@ -79,7 +79,7 @@ export class PeopleComponent implements OnInit {
this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId;
const organization = await this.userService.getOrganization(this.organizationId);
if (!organization.isAdmin) {
if (!organization.canManageUsers) {
this.router.navigate(['../collections'], { relativeTo: this.route });
return;
}

View File

@@ -1,4 +1,4 @@
<app-callout [type]="'warning'">
<app-callout *ngIf="userCanAccessBusinessPortal" [type]="'warning'">
<p>{{'webPoliciesDeprecationWarning' | i18n}}</p>
<button type="button" class="btn btn-outline-secondary"
(click)="goToEnterprisePortal()">{{'businessPortal' | i18n}}</button>

View File

@@ -37,13 +37,14 @@ export class PoliciesComponent implements OnInit {
// Remove when removing deprecation warning
enterpriseTokenPromise: Promise<any>;
userCanAccessBusinessPortal = false;
private enterpriseUrl: string;
private modal: ModalComponent = null;
private orgPolicies: PolicyResponse[];
private policiesEnabledMap: Map<PolicyType, boolean> = new Map<PolicyType, boolean>();
constructor(private apiService: ApiService, private route: ActivatedRoute,
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
private platformUtilsService: PlatformUtilsService, private userService: UserService,
@@ -57,6 +58,7 @@ export class PoliciesComponent implements OnInit {
this.router.navigate(['/organizations', this.organizationId]);
return;
}
this.userCanAccessBusinessPortal = organization.canAccessBusinessPortal;
this.policies = [
{
name: this.i18nService.t('twoStepLogin'),
@@ -93,8 +95,37 @@ export class PoliciesComponent implements OnInit {
enabled: false,
display: organization.useSso,
},
{
name: this.i18nService.t('personalOwnership'),
description: this.i18nService.t('personalOwnershipPolicyDesc'),
type: PolicyType.PersonalOwnership,
enabled: false,
display: true,
},
];
await this.load();
// Handle policies component launch from Event message
const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => {
if (qParams.policyId != null) {
const policyIdFromEvents: string = qParams.policyId;
for (const orgPolicy of this.orgPolicies) {
if (orgPolicy.id === policyIdFromEvents) {
for (let i = 0; i < this.policies.length; i++) {
if (this.policies[i].type === orgPolicy.type) {
this.edit(this.policies[i]);
break;
}
}
break;
}
}
}
if (queryParamsSub != null) {
queryParamsSub.unsubscribe();
}
});
});
// Remove when removing deprecation warning

View File

@@ -29,11 +29,14 @@
{{'requireSsoExemption' | i18n}}
</app-callout>
</ng-container>
<app-callout type="warning" *ngIf="type === policyType.PersonalOwnership">
{{'personalOwnershipExemption' | i18n}}
</app-callout>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="enabled" [(ngModel)]="enabled"
name="Enabled">
<label class="form-check-label" for="enabled">{{'enabled' | i18n}}</label>
<label class="form-check-label" for="enabled">{{checkboxDesc}}</label>
</div>
</div>
<ng-container *ngIf="type === policyType.MasterPassword">

View File

@@ -172,10 +172,16 @@ export class PolicyEditComponent implements OnInit {
}
}
get checkboxDesc(): string {
return this.type === PolicyType.PersonalOwnership ? this.i18nService.t('personalOwnershipCheckboxDesc') :
this.i18nService.t('enabled');
}
private preValidate(): boolean {
switch (this.type) {
case PolicyType.RequireSso:
if (!this.enabled) { // Don't need prevalidation checks if submitting to disable
// Don't need prevalidation checks if submitting to disable
if (!this.enabled) {
return true;
}
// Have SingleOrg policy enabled?
@@ -186,6 +192,19 @@ export class PolicyEditComponent implements OnInit {
}
return true;
case PolicyType.SingleOrg:
// Don't need prevalidation checks if submitting to enable
if (this.enabled) {
return true;
}
// If RequireSso Policy is enabled prevent submittal
if (this.policiesEnabledMap.has(PolicyType.RequireSso)
&& this.policiesEnabledMap.get(PolicyType.RequireSso)) {
this.toasterService.popAsync('error', null, this.i18nService.t('disableRequireSsoError'));
return false;
}
return true;
default:
return true;
}

View File

@@ -63,8 +63,127 @@
<small>{{'ownerDesc' | i18n}}</small>
</label>
</div>
<div class="form-check mt-2 form-check-block">
<input class="form-check-input" type="radio" name="userType" id="userTypeCustom"
[value]="organizationUserType.Custom" [(ngModel)]="type">
<label class="form-check-label" for="userTypeCustom">
{{'custom' | i18n}}
<small>{{'customDesc' | i18n}}</small>
</label>
</div>
<ng-container *ngIf="customUserTypeSelected">
<h3 class="mt-4 d-flex">
{{'permissions' | i18n}}
</h3>
<div class="row">
<div class="col-6">
<div class="mb-3">
<label class="font-weight-bold mb-0">Manager Permissions</label>
<hr class="my-0 mr-2" />
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" name="manageAssignedCollections"
id="manageAssignedCollections"
[(ngModel)]="permissions.manageAssignedCollections">
<label class="form-check-label font-weight-normal"
for="manageAssignedCollections">
{{'manageAssignedCollections' | i18n}}
</label>
</div>
</div>
</div>
</div>
<div class="col-6">
<div class="mb-3">
<label class="font-weight-bold mb-0">Admin Permissions</label>
<hr class="my-0 mr-2" />
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" name="accessBusinessPortal"
id="accessBusinessPortal" [(ngModel)]="permissions.accessBusinessPortal">
<label class="form-check-label font-weight-normal" for="accessBusinessPortal">
{{'accessBusinessPortal' | i18n}}
</label>
</div>
</div>
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" name="accessEventLogs"
id="accessEventLogs" [(ngModel)]="permissions.accessEventLogs">
<label class="form-check-label font-weight-normal" for="accessEventLogs">
{{'accessEventLogs' | i18n}}
</label>
</div>
</div>
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" name="accessImportExport"
id="accessImportExport" [(ngModel)]="permissions.accessImportExport">
<label class="form-check-label font-weight-normal" for="accessImportExport">
{{'accessImportExport' | i18n}}
</label>
</div>
</div>
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" name="accessReports"
id="accessReports" [(ngModel)]="permissions.accessReports">
<label class="form-check-label font-weight-normal" for="accessReports">
{{'accessReports' | i18n}}
</label>
</div>
</div>
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" name="manageAllCollections"
id="manageAllCollections" [(ngModel)]="permissions.manageAllCollections">
<label class="form-check-label font-weight-normal" for="manageAllCollections">
{{'manageAllCollections' | i18n}}
</label>
</div>
</div>
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" name="manageGroups"
id="manageGroups" [(ngModel)]="permissions.manageGroups">
<label class="form-check-label font-weight-normal" for="manageGroups">
{{'manageGroups' | i18n}}
</label>
</div>
</div>
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" name="manageSso"
id="managePolicies" [(ngModel)]="permissions.manageSso">
<label class="form-check-label font-weight-normal" for="manageSso">
{{'manageSso' | i18n}}
</label>
</div>
</div>
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" name="managePolicies"
id="managePolicies" [(ngModel)]="permissions.managePolicies">
<label class="form-check-label font-weight-normal" for="managePolicies">
{{'managePolicies' | i18n}}
</label>
</div>
</div>
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" name="manageUsers"
id="manageUsers" [(ngModel)]="permissions.manageUsers">
<label class="form-check-label font-weight-normal" for="manageUsers">
{{'manageUsers' | i18n}}
</label>
</div>
</div>
</div>
</div>
</div>
</ng-container>
<h3 class="mt-4 d-flex">
<div class="mb-2">
<div class="mb-3">
{{'accessControl' | i18n}}
<a target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}"
href="https://bitwarden.com/help/article/user-types-access-control/#access-control">

View File

@@ -23,6 +23,7 @@ import { CollectionDetailsResponse } from 'jslib/models/response/collectionRespo
import { CollectionView } from 'jslib/models/view/collectionView';
import { OrganizationUserType } from 'jslib/enums/organizationUserType';
import { PermissionsApi } from 'jslib/models/api/permissionsApi';
@Component({
selector: 'app-user-add-edit',
@@ -40,12 +41,18 @@ export class UserAddEditComponent implements OnInit {
title: string;
emails: string;
type: OrganizationUserType = OrganizationUserType.User;
permissions = new PermissionsApi();
showCustom = false;
access: 'all' | 'selected' = 'selected';
collections: CollectionView[] = [];
formPromise: Promise<any>;
deletePromise: Promise<any>;
organizationUserType = OrganizationUserType;
get customUserTypeSelected(): boolean {
return this.type === OrganizationUserType.Custom;
}
constructor(private apiService: ApiService, private i18nService: I18nService,
private analytics: Angulartics2, private toasterService: ToasterService,
private collectionService: CollectionService, private platformUtilsService: PlatformUtilsService) { }
@@ -61,6 +68,9 @@ export class UserAddEditComponent implements OnInit {
const user = await this.apiService.getOrganizationUser(this.organizationId, this.organizationUserId);
this.access = user.accessAll ? 'all' : 'selected';
this.type = user.type;
if (user.type === OrganizationUserType.Custom) {
this.permissions = user.permissions;
}
if (user.collections != null && this.collections != null) {
user.collections.forEach((s) => {
const collection = this.collections.filter((c) => c.id === s.id);
@@ -97,6 +107,40 @@ export class UserAddEditComponent implements OnInit {
this.collections.forEach((c) => this.check(c, select));
}
setRequestPermissions(p: PermissionsApi, clearPermissions: boolean) {
p.accessBusinessPortal = clearPermissions ?
false :
this.permissions.accessBusinessPortal;
p.accessEventLogs = this.permissions.accessEventLogs = clearPermissions ?
false :
this.permissions.accessEventLogs;
p.accessImportExport = clearPermissions ?
false :
this.permissions.accessImportExport;
p.accessReports = clearPermissions ?
false :
this.permissions.accessReports;
p.manageAllCollections = clearPermissions ?
false :
this.permissions.manageAllCollections;
p.manageAssignedCollections = clearPermissions ?
false :
this.permissions.manageAssignedCollections;
p.manageGroups = clearPermissions ?
false :
this.permissions.manageGroups;
p.manageSso = clearPermissions ?
false :
this.permissions.manageSso;
p.managePolicies = clearPermissions ?
false :
this.permissions.managePolicies;
p.manageUsers = clearPermissions ?
false :
this.permissions.manageUsers;
return p;
}
async submit() {
let collections: SelectionReadOnlyRequest[] = null;
if (this.access !== 'all') {
@@ -110,6 +154,7 @@ export class UserAddEditComponent implements OnInit {
request.accessAll = this.access === 'all';
request.type = this.type;
request.collections = collections;
request.permissions = this.setRequestPermissions(request.permissions ?? new PermissionsApi(), request.type !== OrganizationUserType.Custom);
this.formPromise = this.apiService.putOrganizationUser(this.organizationId, this.organizationUserId,
request);
} else {
@@ -117,6 +162,7 @@ export class UserAddEditComponent implements OnInit {
request.emails = this.emails.trim().split(/\s*,\s*/);
request.accessAll = this.access === 'all';
request.type = this.type;
request.permissions = this.setRequestPermissions(request.permissions ?? new PermissionsApi(), request.type !== OrganizationUserType.Custom);
request.collections = collections;
this.formPromise = this.apiService.postOrganizationUserInvite(this.organizationId, request);
}
@@ -148,4 +194,5 @@ export class UserAddEditComponent implements OnInit {
this.onDeletedUser.emit();
} catch { }
}
}

View File

@@ -16,8 +16,6 @@ import { EventType } from 'jslib/enums/eventType';
templateUrl: '../../tools/export.component.html',
})
export class ExportComponent extends BaseExportComponent {
organizationId: string;
constructor(cryptoService: CryptoService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, exportService: ExportService,
eventService: EventService, private route: ActivatedRoute) {

View File

@@ -14,12 +14,15 @@ import {
} from '../../tools/exposed-passwords-report.component';
import { CipherView } from 'jslib/models/view/cipherView';
import { Cipher } from 'jslib/models/domain/cipher';
@Component({
selector: 'app-exposed-passwords-report',
templateUrl: '../../tools/exposed-passwords-report.component.html',
})
export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportComponent {
manageableCiphers: Cipher[];
constructor(cipherService: CipherService, auditService: AuditService,
componentFactoryResolver: ComponentFactoryResolver, messagingService: MessagingService,
userService: UserService, private route: ActivatedRoute) {
@@ -29,6 +32,7 @@ export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportC
ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.organization = await this.userService.getOrganization(params.organizationId);
this.manageableCiphers = await this.cipherService.getAll();
super.ngOnInit();
});
}
@@ -36,4 +40,8 @@ export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportC
getAllCiphers(): Promise<CipherView[]> {
return this.cipherService.getAllFromApiForOrganization(this.organization.id);
}
canManageCipher(c: CipherView): boolean {
return this.manageableCiphers.some(x => x.id === c.id);
}
}

View File

@@ -8,6 +8,8 @@ import { CipherService } from 'jslib/abstractions/cipher.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { UserService } from 'jslib/abstractions/user.service';
import { Cipher } from 'jslib/models/domain/cipher';
import { CipherView } from 'jslib/models/view/cipherView';
import {
@@ -19,6 +21,8 @@ import {
templateUrl: '../../tools/reused-passwords-report.component.html',
})
export class ReusedPasswordsReportComponent extends BaseReusedPasswordsReportComponent {
manageableCiphers: Cipher[];
constructor(cipherService: CipherService, componentFactoryResolver: ComponentFactoryResolver,
messagingService: MessagingService, userService: UserService,
private route: ActivatedRoute) {
@@ -28,6 +32,7 @@ export class ReusedPasswordsReportComponent extends BaseReusedPasswordsReportCom
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.organization = await this.userService.getOrganization(params.organizationId);
this.manageableCiphers = await this.cipherService.getAll();
await super.ngOnInit();
});
}
@@ -35,4 +40,8 @@ export class ReusedPasswordsReportComponent extends BaseReusedPasswordsReportCom
getAllCiphers(): Promise<CipherView[]> {
return this.cipherService.getAllFromApiForOrganization(this.organization.id);
}
canManageCipher(c: CipherView): boolean {
return this.manageableCiphers.some(x => x.id === c.id);
}
}

View File

@@ -1,48 +1,54 @@
<div class="container page-content">
<div class="row">
<div class="col-3">
<div class="card mb-4">
<div class="card-header">{{'tools' | i18n}}</div>
<div class="list-group list-group-flush">
<a routerLink="import" class="list-group-item" routerLinkActive="active">
{{'importData' | i18n}}
</a>
<a routerLink="export" class="list-group-item" routerLinkActive="active">
{{'exportVault' | i18n}}
</a>
</div>
</div>
<div class="card">
<div class="card-header d-flex">
{{'reports' | i18n}}
<div class="ml-auto">
<a href="#" appStopClick class="badge badge-primary" *ngIf="!accessReports"
(click)="upgradeOrganization()">
{{'upgrade' | i18n}}
<ng-container *ngIf="loading">
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'loading' | i18n}}</span>
</ng-container>
<ng-container *ngIf="!loading">
<div class="row">
<div class="col-3">
<div class="card mb-4" *ngIf="organization.canAccessImportExport">
<div class="card-header">{{'tools' | i18n}}</div>
<div class="list-group list-group-flush">
<a routerLink="import" class="list-group-item" routerLinkActive="active">
{{'importData' | i18n}}
</a>
<a routerLink="export" class="list-group-item" routerLinkActive="active">
{{'exportVault' | i18n}}
</a>
</div>
</div>
<div class="list-group list-group-flush">
<a routerLink="exposed-passwords-report" class="list-group-item" routerLinkActive="active">
{{'exposedPasswordsReport' | i18n}}
</a>
<a routerLink="reused-passwords-report" class="list-group-item" routerLinkActive="active">
{{'reusedPasswordsReport' | i18n}}
</a>
<a routerLink="weak-passwords-report" class="list-group-item" routerLinkActive="active">
{{'weakPasswordsReport' | i18n}}
</a>
<a routerLink="unsecured-websites-report" class="list-group-item" routerLinkActive="active">
{{'unsecuredWebsitesReport' | i18n}}
</a>
<a routerLink="inactive-two-factor-report" class="list-group-item" routerLinkActive="active">
{{'inactive2faReport' | i18n}}
</a>
<div class="card" *ngIf="organization.canAccessReports">
<div class="card-header d-flex">
{{'reports' | i18n}}
<div class="ml-auto">
<a href="#" appStopClick class="badge badge-primary" *ngIf="!accessReports"
(click)="upgradeOrganization()">
{{'upgrade' | i18n}}
</a>
</div>
</div>
<div class="list-group list-group-flush">
<a routerLink="exposed-passwords-report" class="list-group-item" routerLinkActive="active">
{{'exposedPasswordsReport' | i18n}}
</a>
<a routerLink="reused-passwords-report" class="list-group-item" routerLinkActive="active">
{{'reusedPasswordsReport' | i18n}}
</a>
<a routerLink="weak-passwords-report" class="list-group-item" routerLinkActive="active">
{{'weakPasswordsReport' | i18n}}
</a>
<a routerLink="unsecured-websites-report" class="list-group-item" routerLinkActive="active">
{{'unsecuredWebsitesReport' | i18n}}
</a>
<a routerLink="inactive-two-factor-report" class="list-group-item" routerLinkActive="active">
{{'inactive2faReport' | i18n}}
</a>
</div>
</div>
</div>
<div class="col-9">
<router-outlet></router-outlet>
</div>
</div>
<div class="col-9">
<router-outlet></router-outlet>
</div>
</div>
</ng-container>
</div>

View File

@@ -13,6 +13,7 @@ import { UserService } from 'jslib/abstractions/user.service';
export class ToolsComponent {
organization: Organization;
accessReports = false;
loading = true;
constructor(private route: ActivatedRoute, private userService: UserService,
private messagingService: MessagingService) { }
@@ -23,6 +24,7 @@ export class ToolsComponent {
// TODO: Maybe we want to just make sure they are not on a free plan? Just compare useTotp for now
// since all paid plans include useTotp
this.accessReports = this.organization.useTotp;
this.loading = false;
});
}

View File

@@ -9,6 +9,8 @@ import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { UserService } from 'jslib/abstractions/user.service';
import { Cipher } from 'jslib/models/domain/cipher';
import { CipherView } from 'jslib/models/view/cipherView';
import {
@@ -20,6 +22,8 @@ import {
templateUrl: '../../tools/weak-passwords-report.component.html',
})
export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportComponent {
manageableCiphers: Cipher[];
constructor(cipherService: CipherService, passwordGenerationService: PasswordGenerationService,
componentFactoryResolver: ComponentFactoryResolver, messagingService: MessagingService,
userService: UserService, private route: ActivatedRoute) {
@@ -29,6 +33,7 @@ export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportCompone
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.organization = await this.userService.getOrganization(params.organizationId);
this.manageableCiphers = await this.cipherService.getAll();
await super.ngOnInit();
});
}
@@ -36,4 +41,8 @@ export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportCompone
getAllCiphers(): Promise<CipherView[]> {
return this.cipherService.getAllFromApiForOrganization(this.organization.id);
}
canManageCipher(c: CipherView): boolean {
return this.manageableCiphers.some(x => x.id === c.id);
}
}

View File

@@ -10,6 +10,7 @@ import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { PolicyService } from 'jslib/abstractions/policy.service';
import { StateService } from 'jslib/abstractions/state.service';
import { TotpService } from 'jslib/abstractions/totp.service';
import { UserService } from 'jslib/abstractions/user.service';
@@ -36,16 +37,16 @@ export class AddEditComponent extends BaseAddEditComponent {
userService: UserService, collectionService: CollectionService,
totpService: TotpService, passwordGenerationService: PasswordGenerationService,
private apiService: ApiService, messagingService: MessagingService,
eventService: EventService) {
eventService: EventService, policyService: PolicyService) {
super(cipherService, folderService, i18nService, platformUtilsService, auditService, stateService,
userService, collectionService, totpService, passwordGenerationService, messagingService,
eventService);
eventService, policyService);
}
protected allowOwnershipAssignment() {
if (this.ownershipOptions != null && this.ownershipOptions.length > 1) {
if (this.ownershipOptions != null && (this.ownershipOptions.length > 1 || !this.allowPersonal)) {
if (this.organization != null) {
return this.cloneMode && this.organization.isAdmin;
return this.cloneMode && this.organization.canManageAllCollections;
} else {
return !this.editMode || this.cloneMode;
}
@@ -54,14 +55,14 @@ export class AddEditComponent extends BaseAddEditComponent {
}
protected loadCollections() {
if (!this.organization.isAdmin) {
if (!this.organization.canManageAllCollections) {
return super.loadCollections();
}
return Promise.resolve(this.collections);
}
protected async loadCipher() {
if (!this.organization.isAdmin) {
if (!this.organization.canManageAllCollections) {
return await super.loadCipher();
}
const response = await this.apiService.getCipherAdmin(this.cipherId);
@@ -71,14 +72,14 @@ export class AddEditComponent extends BaseAddEditComponent {
}
protected encryptCipher() {
if (!this.organization.isAdmin) {
if (!this.organization.canManageAllCollections) {
return super.encryptCipher();
}
return this.cipherService.encrypt(this.cipher, null, this.originalCipher);
}
protected async saveCipher(cipher: Cipher) {
if (!this.organization.isAdmin || cipher.organizationId == null) {
if (!this.organization.canManageAllCollections || cipher.organizationId == null) {
return super.saveCipher(cipher);
}
if (this.editMode && !this.cloneMode) {
@@ -91,7 +92,7 @@ export class AddEditComponent extends BaseAddEditComponent {
}
protected async deleteCipher() {
if (!this.organization.isAdmin) {
if (!this.organization.canManageAllCollections) {
return super.deleteCipher();
}
return this.cipher.isDeleted ? this.apiService.deleteCipherAdmin(this.cipherId)

View File

@@ -29,13 +29,13 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
}
protected async reupload(attachment: AttachmentView) {
if (this.organization.isAdmin && this.showFixOldAttachments(attachment)) {
if (this.organization.canManageAllCollections && this.showFixOldAttachments(attachment)) {
await super.reuploadCipherAttachment(attachment, true);
}
}
protected async loadCipher() {
if (!this.organization.isAdmin) {
if (!this.organization.canManageAllCollections) {
return await super.loadCipher();
}
const response = await this.apiService.getCipherAdmin(this.cipherId);
@@ -43,17 +43,17 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
}
protected saveCipherAttachment(file: File) {
return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file, this.organization.isAdmin);
return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file, this.organization.canManageAllCollections);
}
protected deleteCipherAttachment(attachmentId: string) {
if (!this.organization.isAdmin) {
if (!this.organization.canManageAllCollections) {
return super.deleteCipherAttachment(attachmentId);
}
return this.apiService.deleteCipherAttachmentAdmin(this.cipherId, attachmentId);
}
protected showFixOldAttachments(attachment: AttachmentView) {
return attachment.key == null && this.organization.isAdmin;
return attachment.key == null && this.organization.canManageAllCollections;
}
}

View File

@@ -13,6 +13,8 @@ import { EventService } from 'jslib/abstractions/event.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { SearchService } from 'jslib/abstractions/search.service';
import { TotpService } from 'jslib/abstractions/totp.service';
import { UserService } from 'jslib/abstractions/user.service';
import { Organization } from 'jslib/models/domain/organization';
import { CipherView } from 'jslib/models/view/cipherView';
@@ -34,13 +36,13 @@ export class CiphersComponent extends BaseCiphersComponent {
constructor(searchService: SearchService, analytics: Angulartics2,
toasterService: ToasterService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, cipherService: CipherService,
private apiService: ApiService, eventService: EventService) {
private apiService: ApiService, eventService: EventService, totpService: TotpService, userService: UserService) {
super(searchService, analytics, toasterService, i18nService, platformUtilsService,
cipherService, eventService);
cipherService, eventService, totpService, userService);
}
async load(filter: (cipher: CipherView) => boolean = null) {
if (!this.organization.isAdmin) {
if (!this.organization.canManageAllCollections) {
await super.load(filter, this.deleted);
return;
}
@@ -51,7 +53,7 @@ export class CiphersComponent extends BaseCiphersComponent {
}
async applyFilter(filter: (cipher: CipherView) => boolean = null) {
if (this.organization.isAdmin) {
if (this.organization.canManageAllCollections) {
await super.applyFilter(filter);
} else {
const f = (c: CipherView) => c.organizationId === this.organization.id && (filter == null || filter(c));
@@ -60,7 +62,7 @@ export class CiphersComponent extends BaseCiphersComponent {
}
async search(timeout: number = null) {
if (!this.organization.isAdmin) {
if (!this.organization.canManageAllCollections) {
return super.search(timeout);
}
this.searchPending = false;
@@ -87,13 +89,13 @@ export class CiphersComponent extends BaseCiphersComponent {
}
protected deleteCipher(id: string) {
if (!this.organization.isAdmin) {
if (!this.organization.canManageAllCollections) {
return super.deleteCipher(id, this.deleted);
}
return this.deleted ? this.apiService.deleteCipherAdmin(id) : this.apiService.putDeleteCipherAdmin(id);
}
protected showFixOldAttachments(c: CipherView) {
return this.organization.isAdmin && c.hasOldAttachments;
return this.organization.canManageAllCollections && c.hasOldAttachments;
}
}

View File

@@ -28,7 +28,7 @@ export class CollectionsComponent extends BaseCollectionsComponent {
}
protected async loadCipher() {
if (!this.organization.isAdmin) {
if (!this.organization.canManageAllCollections) {
return await super.loadCipher();
}
const response = await this.apiService.getCipherAdmin(this.cipherId);
@@ -36,21 +36,21 @@ export class CollectionsComponent extends BaseCollectionsComponent {
}
protected loadCipherCollections() {
if (!this.organization.isAdmin) {
if (!this.organization.canManageAllCollections) {
return super.loadCipherCollections();
}
return this.collectionIds;
}
protected loadCollections() {
if (!this.organization.isAdmin) {
if (!this.organization.canManageAllCollections) {
return super.loadCollections();
}
return Promise.resolve(this.collections);
}
protected saveCollections() {
if (this.organization.isAdmin) {
if (this.organization.canManageAllCollections) {
const request = new CipherCollectionsRequest(this.cipherDomain.collectionIds);
return this.apiService.putCipherCollectionsAdmin(this.cipherId, request);
} else {

View File

@@ -29,7 +29,7 @@ export class GroupingsComponent extends BaseGroupingsComponent {
}
async loadCollections() {
if (!this.organization.isAdmin) {
if (!this.organization.canManageAllCollections) {
await super.loadCollections(this.organization.id);
return;
}

View File

@@ -69,7 +69,7 @@ export class VaultComponent implements OnInit, OnDestroy {
const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => {
this.ciphersComponent.searchText = this.groupingsComponent.searchText = qParams.search;
if (!this.organization.isAdmin) {
if (!this.organization.canManageAllCollections) {
await this.syncService.fullSync(false);
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => {
@@ -233,7 +233,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.modal = this.collectionsModalRef.createComponent(factory).instance;
const childComponent = this.modal.show<CollectionsComponent>(CollectionsComponent, this.collectionsModalRef);
if (this.organization.isAdmin) {
if (this.organization.canManageAllCollections) {
childComponent.collectionIds = cipher.collectionIds;
childComponent.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
}
@@ -253,7 +253,7 @@ export class VaultComponent implements OnInit, OnDestroy {
const component = this.editCipher(null);
component.organizationId = this.organization.id;
component.type = this.type;
if (this.organization.isAdmin) {
if (this.organization.canManageAllCollections) {
component.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
}
if (this.collectionId != null) {
@@ -296,7 +296,7 @@ export class VaultComponent implements OnInit, OnDestroy {
const component = this.editCipher(cipher);
component.cloneMode = true;
component.organizationId = this.organization.id;
if (this.organization.isAdmin) {
if (this.organization.canManageAllCollections) {
component.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
}
// Regardless of Admin state, the collection Ids need to passed manually as they are not assigned value

View File

@@ -25,6 +25,12 @@
</button>
</div>
</div>
<div class="card-body" *ngIf="!loading && unavailable">
{{'sendAccessUnavailable' | i18n}}
</div>
<div class="card-body" *ngIf="!loading && error">
{{'unexpectedError' | i18n}}
</div>
<div class="card-body" *ngIf="!loading && !passwordRequired && send">
<p class="text-center"><b>{{send.name}}</b></p>
<hr>
@@ -33,7 +39,7 @@
<app-callout *ngIf="send.text.hidden" type="tip">{{'sendHiddenByDefault' | i18n}}</app-callout>
<div class="form-group">
<textarea id="text" rows="8" name="Text" [(ngModel)]="sendText" class="form-control"
readonly (click)="selectText()"></textarea>
readonly></textarea>
</div>
<button class="btn btn-block btn-link" type="button" (click)="toggleText()"
*ngIf="send.text.hidden">

View File

@@ -37,6 +37,8 @@ export class AccessComponent implements OnInit {
formPromise: Promise<SendAccessResponse>;
password: string;
showText = false;
unavailable = false;
error = false;
private id: string;
private key: string;
@@ -93,10 +95,6 @@ export class AccessComponent implements OnInit {
this.downloading = false;
}
selectText() {
(document.getElementById('text') as HTMLInputElement).select();
}
copyText() {
this.platformUtilsService.copyToClipboard(this.send.text.text);
this.platformUtilsService.showToast('success', null,
@@ -108,6 +106,8 @@ export class AccessComponent implements OnInit {
}
async load() {
this.unavailable = false;
this.error = false;
const keyArray = Utils.fromUrlB64ToArray(this.key);
const accessRequest = new SendAccessRequest();
if (this.password != null) {
@@ -131,6 +131,10 @@ export class AccessComponent implements OnInit {
if (e instanceof ErrorResponse) {
if (e.statusCode === 401) {
this.passwordRequired = true;
} else if (e.statusCode === 404) {
this.unavailable = true;
} else {
this.error = true;
}
}
}

View File

@@ -12,7 +12,8 @@
<div class="row" *ngIf="!editMode">
<div class="col-6 form-group">
<label for="type">{{'whatTypeOfSend' | i18n}}</label>
<select id="type" name="Type" [(ngModel)]="send.type" class="form-control" appAutofocus>
<select id="type" name="Type" [(ngModel)]="send.type" class="form-control" appAutofocus
(change)='typeChanged()'>
<option *ngFor="let o of typeOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
@@ -34,7 +35,7 @@
<div class="form-check">
<input class="form-check-input" type="checkbox" [(ngModel)]="send.text.hidden"
id="text-hidden" name="Text.Hidden">
<label class="form-check-label" for="text-hidden">{{'cfTypeHidden' | i18n}}</label>
<label class="form-check-label" for="text-hidden">{{'textHiddenByDefault' | i18n}}</label>
</div>
</div>
</ng-container>
@@ -52,29 +53,55 @@
</div>
</div>
</ng-container>
<h3 class="mt-4">{{'options' | i18n}}</h3>
<h3 class="mt-5">{{'options' | i18n}}</h3>
<div class="row">
<div class="col-6 form-group">
<label for="deletionDate">{{'deletionDate' | i18n}}</label>
<input id="deletionDate" class="form-control" type="datetime-local" name="DeletionDate"
[(ngModel)]="deletionDate" required>
<div *ngIf="!editMode">
<select id="deletionDate" name="DeletionDateSelect" [(ngModel)]="deletionDateSelect"
class="form-control" required>
<option *ngFor="let o of deletionDateOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
<input id="deletionDateCustom" class="form-control mt-1" type="datetime-local"
name="DeletionDate" [(ngModel)]="deletionDate" required *ngIf="deletionDateSelect === 0"
placeholder="MM/DD/YYYY HH:MM AM/PM">
</div>
<div *ngIf="editMode">
<input id="deletionDate" class="form-control" type="datetime-local" name="DeletionDate"
[(ngModel)]="deletionDate" required placeholder="MM/DD/YYYY HH:MM AM/PM">
</div>
<div class="form-text text-muted small">{{'deletionDateDesc' | i18n}}</div>
</div>
<div class="col-6 form-group">
<div class="d-flex">
<label for="expirationDate">{{'expirationDate' | i18n}}</label>
<a href="#" appStopClick (click)="clearExpiration()" class="ml-auto">
<a href="#" appStopClick (click)="clearExpiration()" class="ml-auto" *ngIf="editMode">
{{'clear' | i18n}}
</a>
</div>
<input id="expirationDate" class="form-control" type="datetime-local" name="ExpirationDate"
[(ngModel)]="expirationDate">
<div *ngIf="!editMode">
<select id="expirationDate" name="ExpirationDateSelect" [(ngModel)]="expirationDateSelect"
class="form-control" required>
<option *ngFor="let o of expirationDateOptions" [ngValue]="o.value">{{o.name}}
</option>
</select>
<input id="expirationDateCustom" class="form-control mt-1" type="datetime-local"
name="ExpirationDate" [(ngModel)]="expirationDate" required
*ngIf="expirationDateSelect === 0" placeholder="MM/DD/YYYY HH:MM AM/PM">
</div>
<div *ngIf="editMode">
<input id="expirationDate" class="form-control" type="datetime-local" name="ExpirationDate"
[(ngModel)]="expirationDate" placeholder="MM/DD/YYYY HH:MM AM/PM">
</div>
<div class="form-text text-muted small">{{'expirationDateDesc' | i18n}}</div>
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="maxAccessCount">{{'maxAccessCount' | i18n}}</label>
<input id="maxAccessCount" class="form-control" type="number" name="MaxAccessCount"
[(ngModel)]="send.maxAccessCount">
[(ngModel)]="send.maxAccessCount" min="1">
<div class="form-text text-muted small">{{'maxAccessCountDesc' | i18n}}</div>
</div>
<div class="col-6 form-group" *ngIf="editMode">
<label for="accessCount">{{'currentAccessCount' | i18n}}</label>
@@ -88,19 +115,22 @@
<label for="password" *ngIf="hasPassword">{{'newPassword' | i18n}}</label>
<input id="password" class="form-control" type="password" name="Password"
[(ngModel)]="password">
<div class="form-text text-muted small">{{'sendPasswordDesc' | i18n}}</div>
</div>
</div>
<div class="form-group">
<label for="notes">{{'notes' | i18n}}</label>
<textarea id="notes" name="Notes" rows="6" [(ngModel)]="send.notes" class="form-control"></textarea>
<div class="form-text text-muted small">{{'sendNotesDesc' | i18n}}</div>
</div>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" [(ngModel)]="send.disabled" id="disabled"
name="Disabled">
<label class="form-check-label" for="disabled">{{'disabled' | i18n}}</label>
<label class="form-check-label" for="disabled">{{'disableThisSend' | i18n}}</label>
</div>
</div>
<h3 class="mt-5" *ngIf="link">{{'share' | i18n}}</h3>
<div class="form-group" *ngIf="link">
<label for="link">{{'sendLink' | i18n}}</label>
<input type="text" readonly id="link" name="Link" [(ngModel)]="link" class="form-control">

View File

@@ -10,20 +10,19 @@ import { Component } from '@angular/core';
import { SendType } from 'jslib/enums/sendType';
import { ApiService } from 'jslib/abstractions/api.service';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { SendService } from 'jslib/abstractions/send.service';
import { UserService } from 'jslib/abstractions/user.service';
import { SendView } from 'jslib/models/view/sendView';
import { SendFileView } from 'jslib/models/view/sendFileView';
import { SendTextView } from 'jslib/models/view/sendTextView';
import { SendView } from 'jslib/models/view/sendView';
import { Send } from 'jslib/models/domain/send';
import { SendData } from 'jslib/models/data/sendData';
@Component({
selector: 'app-send-add-edit',
templateUrl: 'add-edit.component.html',
@@ -48,14 +47,33 @@ export class AddEditComponent {
deletePromise: Promise<any>;
sendType = SendType;
typeOptions: any[];
deletionDateOptions: any[];
expirationDateOptions: any[];
deletionDateSelect = 168;
expirationDateSelect: number = null;
canAccessPremium = true;
premiumRequiredAlertShown = false;
constructor(private i18nService: I18nService, private platformUtilsService: PlatformUtilsService,
private apiService: ApiService, private environmentService: EnvironmentService,
private datePipe: DatePipe, private sendService: SendService) {
private environmentService: EnvironmentService, private datePipe: DatePipe,
private sendService: SendService, private userService: UserService,
private messagingService: MessagingService) {
this.typeOptions = [
{ name: i18nService.t('sendTypeFile'), value: SendType.File },
{ name: i18nService.t('sendTypeText'), value: SendType.Text },
];
this.deletionDateOptions = this.expirationDateOptions = [
{ name: i18nService.t('oneHour'), value: 1 },
{ name: i18nService.t('oneDay'), value: 24 },
{ name: i18nService.t('days', '2'), value: 48 },
{ name: i18nService.t('days', '3'), value: 72 },
{ name: i18nService.t('days', '7'), value: 168 },
{ name: i18nService.t('days', '30'), value: 720 },
{ name: i18nService.t('custom'), value: 0 },
];
this.expirationDateOptions = [
{ name: i18nService.t('never'), value: null },
].concat([...this.deletionDateOptions]);
}
async ngOnInit() {
@@ -71,6 +89,11 @@ export class AddEditComponent {
this.title = this.i18nService.t('createSend');
}
this.canAccessPremium = await this.userService.canAccessPremium();
if (!this.canAccessPremium) {
this.type = SendType.Text;
}
if (this.send == null) {
if (this.editMode) {
const send = await this.loadSend();
@@ -88,10 +111,8 @@ export class AddEditComponent {
this.hasPassword = this.send.password != null && this.send.password.trim() !== '';
// Parse dates
this.deletionDate = this.send.deletionDate == null ? null :
this.datePipe.transform(this.send.deletionDate, 'yyyy-MM-ddTHH:mm');
this.expirationDate = this.send.expirationDate == null ? null :
this.datePipe.transform(this.send.expirationDate, 'yyyy-MM-ddTHH:mm');
this.deletionDate = this.dateToString(this.send.deletionDate);
this.expirationDate = this.dateToString(this.send.expirationDate);
if (this.editMode) {
let webVaultUrl = this.environmentService.getWebVaultUrl();
@@ -127,6 +148,20 @@ export class AddEditComponent {
}
}
if (!this.editMode) {
const now = new Date();
if (this.deletionDateSelect > 0) {
const d = new Date();
d.setHours(now.getHours() + this.deletionDateSelect);
this.deletionDate = this.dateToString(d);
}
if (this.expirationDateSelect != null && this.expirationDateSelect > 0) {
const d = new Date();
d.setHours(now.getHours() + this.expirationDateSelect);
this.expirationDate = this.dateToString(d);
}
}
const encSend = await this.encryptSend(file);
try {
this.formPromise = this.sendService.saveWithServer(encSend);
@@ -158,7 +193,7 @@ export class AddEditComponent {
}
try {
this.deletePromise = this.apiService.deleteSend(this.send.id);
this.deletePromise = this.sendService.deleteWithServer(this.send.id);
await this.deletePromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedSend'));
await this.load();
@@ -166,10 +201,15 @@ export class AddEditComponent {
} catch { }
}
typeChanged() {
if (!this.canAccessPremium && this.send.type === SendType.File && !this.premiumRequiredAlertShown) {
this.premiumRequiredAlertShown = true;
this.messagingService.send('premiumRequired');
}
}
protected async loadSend(): Promise<Send> {
const response = await this.apiService.getSend(this.sendId);
const data = new SendData(response);
return new Send(data);
return this.sendService.get(this.sendId);
}
protected async encryptSend(file: File): Promise<[Send, ArrayBuffer]> {
@@ -189,4 +229,8 @@ export class AddEditComponent {
return sendData;
}
protected dateToString(d: Date) {
return d == null ? null : this.datePipe.transform(d, 'yyyy-MM-ddTHH:mm');
}
}

View File

@@ -62,10 +62,27 @@
</td>
<td class="reduced-lh wrap">
<a href="#" appStopClick appStopProp (click)="editSend(s)">{{s.name}}</a>
<span appStopClick class="badge badge-secondary" *ngIf="s.disabled">
{{'disabled' | i18n}}
</span>
<ng-container *ngIf="s.password">
<i class="fa fa-key" appStopProp title="{{'password' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'password' | i18n}}</span>
</ng-container>
<ng-container *ngIf="s.maxAccessCountReached">
<i class="fa fa-ban" appStopProp title="{{'maxAccessCountReached' | i18n}}"
aria-hidden="true"></i>
<span class="sr-only">{{'maxAccessCountReached' | i18n}}</span>
</ng-container>
<ng-container *ngIf="s.expired">
<i class="fa fa-clock-o" appStopProp title="{{'expired' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'expired' | i18n}}</span>
</ng-container>
<ng-container *ngIf="s.pendingDelete">
<i class="fa fa-trash" appStopProp title="{{'pendingDeletion' | i18n}}"
aria-hidden="true"></i>
<span class="sr-only">{{'pendingDeletion' | i18n}}</span>
</ng-container>
<br>
<small appStopProp>{{s.deletionDate | date:'medium'}}</small>
</td>

View File

@@ -143,7 +143,7 @@ export class SendComponent implements OnInit {
}
try {
this.actionPromise = this.apiService.putSendRemovePassword(s.id);
this.actionPromise = this.sendService.removePasswordWithServer(s.id);
await this.actionPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('removedPassword'));
await this.load();
@@ -164,7 +164,7 @@ export class SendComponent implements OnInit {
}
try {
this.actionPromise = this.apiService.deleteSend(s.id);
this.actionPromise = this.sendService.deleteWithServer(s.id);
await this.actionPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedSend'));
await this.load();

View File

@@ -153,6 +153,8 @@ export class EventService {
case EventType.OrganizationUser_UpdatedGroups:
msg = this.i18nService.t('editedGroupsForUser', this.formatOrgUserId(ev));
break;
case EventType.OrganizationUser_UnlinkedSso:
msg = this.i18nService.t('unlinkedSsoUser', this.formatOrgUserId(ev));
// Org
case EventType.Organization_Updated:
msg = this.i18nService.t('editedOrgSettings');
@@ -165,6 +167,11 @@ export class EventService {
msg = this.i18nService.t('exportedOrganizationVault');
break;
*/
// Policies
case EventType.Policy_Updated:
msg = this.i18nService.t('modifiedPolicy', this.formatPolicyId(ev));
break;
default:
break;
}
@@ -251,6 +258,13 @@ export class EventService {
return a.outerHTML;
}
private formatPolicyId(ev: EventResponse) {
const shortId = this.getShortId(ev.policyId);
const a = this.makeAnchor(shortId);
a.setAttribute('href', '#/organizations/' + ev.organizationId + '/manage/policies?policyId=' + ev.policyId);
return a.outerHTML;
}
private makeAnchor(shortId: string) {
const a = document.createElement('a');
a.title = this.i18nService.t('view');

View File

@@ -7,20 +7,32 @@ import {
import { UserService } from 'jslib/abstractions/user.service';
import { OrganizationUserType } from 'jslib/enums/organizationUserType';
import { Permissions } from 'jslib/enums/permissions';
@Injectable()
export class OrganizationTypeGuardService implements CanActivate {
constructor(private userService: UserService, private router: Router) { }
async canActivate(route: ActivatedRouteSnapshot) {
const org = await this.userService.getOrganization(route.parent.params.organizationId);
const allowedTypes = route.data == null ? null : route.data.allowedTypes as OrganizationUserType[];
if (allowedTypes == null || allowedTypes.indexOf(org.type) === -1) {
this.router.navigate(['/organizations', org.id]);
return false;
const org = await this.userService.getOrganization(route.params.organizationId);
const permissions = route.data == null ? null : route.data.permissions as Permissions[];
if (
(permissions.indexOf(Permissions.AccessBusinessPortal) !== -1 && org.canAccessBusinessPortal) ||
(permissions.indexOf(Permissions.AccessEventLogs) !== -1 && org.canAccessEventLogs) ||
(permissions.indexOf(Permissions.AccessImportExport) !== -1 && org.canAccessImportExport) ||
(permissions.indexOf(Permissions.AccessReports) !== -1 && org.canAccessReports) ||
(permissions.indexOf(Permissions.ManageAllCollections) !== -1 && org.canManageAllCollections) ||
(permissions.indexOf(Permissions.ManageAssignedCollections) !== -1 && org.canManageAssignedCollections) ||
(permissions.indexOf(Permissions.ManageGroups) !== -1 && org.canManageGroups) ||
(permissions.indexOf(Permissions.ManageOrganization) !== -1 && org.isOwner) ||
(permissions.indexOf(Permissions.ManagePolicies) !== -1 && org.canManagePolicies) ||
(permissions.indexOf(Permissions.ManageUsers) !== -1 && org.canManageUsers)
) {
return true;
}
return true;
this.router.navigate(['/organizations', org.id]);
return false;
}
}

View File

@@ -30,6 +30,7 @@ import { AuditService } from 'jslib/services/audit.service';
import { AuthService } from 'jslib/services/auth.service';
import { CipherService } from 'jslib/services/cipher.service';
import { CollectionService } from 'jslib/services/collection.service';
import { ConsoleLogService } from 'jslib/services/consoleLog.service';
import { ConstantsService } from 'jslib/services/constants.service';
import { ContainerService } from 'jslib/services/container.service';
import { CryptoService } from 'jslib/services/crypto.service';
@@ -94,8 +95,10 @@ const storageService: StorageServiceAbstraction = new HtmlStorageService(platfor
const secureStorageService: StorageServiceAbstraction = new MemoryStorageService();
const cryptoFunctionService: CryptoFunctionServiceAbstraction = new WebCryptoFunctionService(window,
platformUtilsService);
const consoleLogService = new ConsoleLogService(false);
const cryptoService = new CryptoService(storageService,
platformUtilsService.isDev() ? storageService : secureStorageService, cryptoFunctionService, platformUtilsService);
platformUtilsService.isDev() ? storageService : secureStorageService, cryptoFunctionService, platformUtilsService,
consoleLogService);
const tokenService = new TokenService(storageService);
const appIdService = new AppIdService(storageService);
const apiService = new ApiService(tokenService, platformUtilsService,
@@ -108,7 +111,7 @@ const cipherService = new CipherService(cryptoService, userService, settingsServ
const folderService = new FolderService(cryptoService, userService, apiService, storageService,
i18nService, cipherService);
const collectionService = new CollectionService(cryptoService, userService, storageService, i18nService);
searchService = new SearchService(cipherService);
searchService = new SearchService(cipherService, consoleLogService);
const policyService = new PolicyService(userService, storageService);
const sendService = new SendService(cryptoService, userService, apiService, storageService,
i18nService, cryptoFunctionService);
@@ -122,11 +125,12 @@ const passwordGenerationService = new PasswordGenerationService(cryptoService, s
const totpService = new TotpService(storageService, cryptoFunctionService);
const containerService = new ContainerService(cryptoService);
const authService = new AuthService(cryptoService, apiService,
userService, tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService);
userService, tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService,
consoleLogService);
const exportService = new ExportService(folderService, cipherService, apiService);
const importService = new ImportService(cipherService, folderService, apiService, i18nService, collectionService);
const notificationsService = new NotificationsService(userService, syncService, appIdService,
apiService, vaultTimeoutService, async () => messagingService.send('logout', { expired: true }));
apiService, vaultTimeoutService, async () => messagingService.send('logout', { expired: true }), consoleLogService);
const environmentService = new EnvironmentService(apiService, storageService, notificationsService);
const auditService = new AuditService(cryptoFunctionService, apiService);
const eventLoggingService = new EventLoggingService(storageService, apiService, userService, cipherService);

View File

@@ -16,10 +16,14 @@ import {
ChangePasswordComponent as BaseChangePasswordComponent,
} from 'jslib/angular/components/change-password.component';
import { EmergencyAccessStatusType } from 'jslib/enums/emergencyAccessStatusType';
import { Utils } from 'jslib/misc/utils';
import { CipherString } from 'jslib/models/domain/cipherString';
import { SymmetricCryptoKey } from 'jslib/models/domain/symmetricCryptoKey';
import { CipherWithIdRequest } from 'jslib/models/request/cipherWithIdRequest';
import { EmergencyAccessUpdateRequest } from 'jslib/models/request/emergencyAccessUpdateRequest';
import { FolderWithIdRequest } from 'jslib/models/request/folderWithIdRequest';
import { PasswordRequest } from 'jslib/models/request/passwordRequest';
import { UpdateKeyRequest } from 'jslib/models/request/updateKeyRequest';
@@ -37,7 +41,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
userService: UserService, passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService, policyService: PolicyService,
private folderService: FolderService, private cipherService: CipherService,
private syncService: SyncService, private apiService: ApiService, ) {
private syncService: SyncService, private apiService: ApiService ) {
super(i18nService, cryptoService, messagingService, userService, passwordGenerationService,
platformUtilsService, policyService);
}
@@ -69,6 +73,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
const result = await this.platformUtilsService.showDialog(
this.i18nService.t('updateEncryptionKeyWarning') + ' ' +
this.i18nService.t('updateEncryptionKeyExportWarning') + ' ' +
this.i18nService.t('rotateEncKeyConfirmation'), this.i18nService.t('rotateEncKeyTitle'),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!result) {
@@ -159,5 +164,32 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
}
await this.apiService.postAccountKey(request);
await this.updateEmergencyAccesses(encKey[0]);
}
private async updateEmergencyAccesses(encKey: SymmetricCryptoKey) {
const emergencyAccess = await this.apiService.getEmergencyAccessTrusted();
const allowedStatuses = [
EmergencyAccessStatusType.Confirmed,
EmergencyAccessStatusType.RecoveryInitiated,
EmergencyAccessStatusType.RecoveryApproved,
];
const filteredAccesses = emergencyAccess.data.filter(d => allowedStatuses.includes(d.status));
for (const details of filteredAccesses) {
const publicKeyResponse = await this.apiService.getUserPublicKey(details.granteeId);
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
const updateRequest = new EmergencyAccessUpdateRequest();
updateRequest.type = details.type;
updateRequest.waitTimeDays = details.waitTimeDays;
updateRequest.keyEncrypted = encryptedKey.encryptedString;
await this.apiService.putEmergencyAccess(details.id, updateRequest);
}
}
}

View File

@@ -5,6 +5,9 @@ import {
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { PlanType } from 'jslib/enums/planType';
import { ProductType } from 'jslib/enums/productType';
import { OrganizationPlansComponent } from './organization-plans.component';
@Component({
@@ -18,8 +21,15 @@ export class CreateOrganizationComponent implements OnInit {
ngOnInit() {
const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => {
if (qParams.plan === 'families' || qParams.plan === 'teams' || qParams.plan === 'enterprise') {
this.orgPlansComponent.plan = qParams.plan;
if (qParams.plan === 'families') {
this.orgPlansComponent.plan = PlanType.FamiliesAnnually;
this.orgPlansComponent.product = ProductType.Families;
} else if (qParams.plan === 'teams') {
this.orgPlansComponent.plan = PlanType.TeamsAnnually;
this.orgPlansComponent.product = ProductType.Teams;
} else if (qParams.plan === 'enterprise') {
this.orgPlansComponent.plan = PlanType.EnterpriseAnnually;
this.orgPlansComponent.product = ProductType.Enterprise;
}
if (queryParamsSub != null) {
queryParamsSub.unsubscribe();

View File

@@ -0,0 +1,76 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="userAddEditTitle">
<div class="modal-dialog modal-lg" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<div class="modal-header">
<h2 class="modal-title" id="userAddEditTitle">
<span class="badge badge-primary" *ngIf="readOnly">{{'premium' | i18n}}</span>
{{title}}
<small class="text-muted" *ngIf="name">{{name}}</small>
</h2>
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body" *ngIf="loading">
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'loading' | i18n}}</span>
</div>
<div class="modal-body" *ngIf="!loading">
<ng-container *ngIf="!editMode">
<p>{{'inviteEmergencyContactDesc' | i18n}}</p>
<div class="form-group mb-4">
<label for="email">{{'email' | i18n}}</label>
<input id="email" class="form-control" type="text" name="Email" [(ngModel)]="email" required>
</div>
</ng-container>
<h3>
{{'userAccess' | i18n}}
<a target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}"
href="https://bitwarden.com/help/article/user-types-access-control/#user-types">
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</h3>
<div class="form-check mt-2 form-check-block">
<input class="form-check-input" type="radio" name="userType" id="emergencyTypeView"
[value]="emergencyAccessType.View" [(ngModel)]="type">
<label class="form-check-label" for="emergencyTypeView">
{{'view' | i18n}}
<small>{{'viewDesc' | i18n}}</small>
</label>
</div>
<div class="form-check mt-2 form-check-block">
<input class="form-check-input" type="radio" name="userType" id="emergencyTypeTakeover"
[value]="emergencyAccessType.Takeover" [(ngModel)]="type" [disabled]="readOnly">
<label class="form-check-label" for="emergencyTypeTakeover">
{{'takeover' | i18n}}
<small>{{'takeoverDesc' | i18n}}</small>
</label>
</div>
<div class="form-group col-6 mt-4">
<label for="waitTime">{{'waitTime' | i18n}}</label>
<select id="waitTime" name="waitTime" [(ngModel)]="waitTime" class="form-control" [disabled]="readOnly">
<option *ngFor="let o of waitTimes" [ngValue]="o.value">{{o.name}}</option>
</select>
<small class="text-muted">{{'waitTimeDesc' | i18n}}</small>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary" [disabled]="loading || readOnly">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true" *ngIf="loading"></i>
<span *ngIf="!loading">{{'save' | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary"
data-dismiss="modal">{{'cancel' | i18n}}</button>
<div class="ml-auto">
<button #deleteBtn type="button" (click)="delete()" class="btn btn-outline-danger"
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
[appApiAction]="deletePromise">
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"
title="{{'loading' | i18n}}" aria-hidden="true"></i>
</button>
</div>
</div>
</form>
</div>
</div>

View File

@@ -0,0 +1,99 @@
import {
Component,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { EmergencyAccessType } from 'jslib/enums/emergencyAccessType';
import { EmergencyAccessInviteRequest } from 'jslib/models/request/emergencyAccessInviteRequest';
import { EmergencyAccessUpdateRequest } from 'jslib/models/request/emergencyAccessUpdateRequest';
@Component({
selector: 'emergency-access-add-edit',
templateUrl: 'emergency-access-add-edit.component.html',
})
export class EmergencyAccessAddEditComponent implements OnInit {
@Input() name: string;
@Input() emergencyAccessId: string;
@Output() onSaved = new EventEmitter();
@Output() onDeleted = new EventEmitter();
loading = true;
readOnly: boolean = false;
editMode: boolean = false;
title: string;
email: string;
type: EmergencyAccessType = EmergencyAccessType.View;
formPromise: Promise<any>;
emergencyAccessType = EmergencyAccessType;
waitTimes: { name: string; value: number; }[];
waitTime: number;
constructor(private apiService: ApiService, private i18nService: I18nService,
private toasterService: ToasterService) { }
async ngOnInit() {
this.editMode = this.loading = this.emergencyAccessId != null;
this.waitTimes = [
{ name: this.i18nService.t('oneDay'), value: 1 },
{ name: this.i18nService.t('days', '2'), value: 2 },
{ name: this.i18nService.t('days', '7'), value: 7 },
{ name: this.i18nService.t('days', '14'), value: 14 },
{ name: this.i18nService.t('days', '30'), value: 30 },
{ name: this.i18nService.t('days', '90'), value: 90 },
];
if (this.editMode) {
this.editMode = true;
this.title = this.i18nService.t('editEmergencyContact');
try {
const emergencyAccess = await this.apiService.getEmergencyAccess(this.emergencyAccessId);
this.type = emergencyAccess.type;
this.waitTime = emergencyAccess.waitTimeDays;
} catch { }
} else {
this.title = this.i18nService.t('inviteEmergencyContact');
this.waitTime = this.waitTimes[2].value;
}
this.loading = false;
}
async submit() {
try {
if (this.editMode) {
const request = new EmergencyAccessUpdateRequest();
request.type = this.type;
request.waitTimeDays = this.waitTime;
this.formPromise = this.apiService.putEmergencyAccess(this.emergencyAccessId, request);
} else {
const request = new EmergencyAccessInviteRequest();
request.email = this.email.trim();
request.type = this.type;
request.waitTimeDays = this.waitTime;
this.formPromise = this.apiService.postEmergencyAccessInvite(request);
}
await this.formPromise;
this.toasterService.popAsync('success', null,
this.i18nService.t(this.editMode ? 'editedUserId' : 'invitedUsers', this.name));
this.onSaved.emit();
} catch { }
}
async delete() {
this.onDeleted.emit();
}
}

View File

@@ -0,0 +1,38 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="confirmUserTitle">
<div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-header">
<h2 class="modal-title" id="confirmUserTitle">
{{'confirmUser' | i18n}}
<small class="text-muted" *ngIf="name">{{name}}</small>
</h2>
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>
{{'fingerprintEnsureIntegrityVerify' | i18n}}
<a href="https://help.bitwarden.com/article/fingerprint-phrase/" target="_blank" rel="noopener">
{{'learnMore' | i18n}}</a>
</p>
<p><code>{{fingerprint}}</code></p>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="dontAskAgain" name="DontAskAgain"
[(ngModel)]="dontAskAgain">
<label class="form-check-label" for="dontAskAgain">
{{'dontAskFingerprintAgain' | i18n}}
</label>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'confirm' | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary"
data-dismiss="modal">{{'cancel' | i18n}}</button>
</div>
</form>
</div>
</div>

View File

@@ -0,0 +1,62 @@
import {
Component,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { ConstantsService } from 'jslib/services/constants.service';
import { ApiService } from 'jslib/abstractions/api.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { Utils } from 'jslib/misc/utils';
@Component({
selector: 'emergency-access-confirm',
templateUrl: 'emergency-access-confirm.component.html',
})
export class EmergencyAccessConfirmComponent implements OnInit {
@Input() name: string;
@Input() userId: string;
@Input() emergencyAccessId: string;
@Input() formPromise: Promise<any>;
@Output() onConfirmed = new EventEmitter();
dontAskAgain = false;
loading = true;
fingerprint: string;
constructor(private apiService: ApiService, private cryptoService: CryptoService,
private storageService: StorageService) { }
async ngOnInit() {
try {
const publicKeyResponse = await this.apiService.getUserPublicKey(this.userId);
if (publicKeyResponse != null) {
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
const fingerprint = await this.cryptoService.getFingerprint(this.userId, publicKey.buffer);
if (fingerprint != null) {
this.fingerprint = fingerprint.join('-');
}
}
} catch { }
this.loading = false;
}
async submit() {
if (this.loading) {
return;
}
if (this.dontAskAgain) {
await this.storageService.save(ConstantsService.autoConfirmFingerprints, true);
}
try {
this.onConfirmed.emit();
} catch { }
}
}

View File

@@ -0,0 +1,44 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="userAddEditTitle">
<div class="modal-dialog modal-lg" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<div class="modal-header">
<h2 class="modal-title" id="userAddEditTitle">
{{'takeover' | i18n}}
<small class="text-muted" *ngIf="name">{{name}}</small>
</h2>
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<app-callout type="warning">{{'loggedOutWarning' | i18n}}</app-callout>
<div class="row">
<div class="col-6">
<div class="form-group">
<label for="masterPassword">{{'newMasterPass' | i18n}}</label>
<input id="masterPassword" type="password" name="NewMasterPasswordHash" class="form-control mb-1"
[(ngModel)]="masterPassword" (input)="updatePasswordStrength()" required appInputVerbatim
autocomplete="new-password">
<app-password-strength [score]="masterPasswordScore" [showText]="true"></app-password-strength>
</div>
</div>
<div class="col-6">
<div class="form-group">
<label for="masterPasswordRetype">{{'confirmNewMasterPass' | i18n}}</label>
<input id="masterPasswordRetype" type="password" name="MasterPasswordRetype"
class="form-control" [(ngModel)]="masterPasswordRetype" required appInputVerbatim
autocomplete="new-password">
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'save' | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'cancel' | i18n}}</button>
</div>
</form>
</div>
</div>

View File

@@ -0,0 +1,81 @@
import {
Component,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { PolicyService } from 'jslib/abstractions/policy.service';
import { UserService } from 'jslib/abstractions/user.service';
import { ChangePasswordComponent } from 'jslib/angular/components/change-password.component';
import { KdfType } from 'jslib/enums/kdfType';
import { SymmetricCryptoKey } from 'jslib/models/domain/symmetricCryptoKey';
import { EmergencyAccessPasswordRequest } from 'jslib/models/request/emergencyAccessPasswordRequest';
@Component({
selector: 'emergency-access-takeover',
templateUrl: 'emergency-access-takeover.component.html',
})
export class EmergencyAccessTakeoverComponent extends ChangePasswordComponent implements OnInit {
@Output() onDone = new EventEmitter();
@Input() emergencyAccessId: string;
@Input() name: string;
@Input() email: string;
@Input() kdf: KdfType;
@Input() kdfIterations: number;
formPromise: Promise<any>;
constructor(i18nService: I18nService, cryptoService: CryptoService,
messagingService: MessagingService, userService: UserService,
passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService, policyService: PolicyService,
private apiService: ApiService, private toasterService: ToasterService) {
super(i18nService, cryptoService, messagingService, userService, passwordGenerationService,
platformUtilsService, policyService);
}
// tslint:disable-next-line
async ngOnInit() { }
async submit() {
if (!await this.strongPassword()) {
return;
}
const takeoverResponse = await this.apiService.postEmergencyAccessTakeover(this.emergencyAccessId);
const oldKeyBuffer = await this.cryptoService.rsaDecrypt(takeoverResponse.keyEncrypted);
const oldEncKey = new SymmetricCryptoKey(oldKeyBuffer);
if (oldEncKey == null) {
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'), this.i18nService.t('unexpectedError'));
return;
}
const key = await this.cryptoService.makeKey(this.masterPassword, this.email, takeoverResponse.kdf, takeoverResponse.kdfIterations);
const masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, key);
const encKey = await this.cryptoService.remakeEncKey(key, oldEncKey);
const request = new EmergencyAccessPasswordRequest();
request.newMasterPasswordHash = masterPasswordHash;
request.key = encKey[1].encryptedString;
this.apiService.postEmergencyAccessPassword(this.emergencyAccessId, request);
try {
this.onDone.emit();
} catch { }
}
}

View File

@@ -0,0 +1,31 @@
<div class="page-header">
<h1>{{'vault' | i18n}}</h1>
</div>
<div class="mt-4">
<ng-container *ngIf="ciphers.length">
<table class="table table-hover table-list table-ciphers">
<tbody>
<tr *ngFor="let c of ciphers">
<td class="table-list-icon">
<app-vault-icon [cipher]="c"></app-vault-icon>
</td>
<td class="reduced-lh wrap">
<a href="#" appStopClick (click)="selectCipher(c)" title="{{'editItem' | i18n}}">{{c.name}}</a>
<ng-container *ngIf="!organization && c.organizationId">
<i class="fa fa-share-alt" appStopProp title="{{'shared' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'shared' | i18n}}</span>
</ng-container>
<ng-container *ngIf="c.hasAttachments">
<i class="fa fa-paperclip" appStopProp title="{{'attachments' | i18n}}"
aria-hidden="true"></i>
<span class="sr-only">{{'attachments' | i18n}}</span>
</ng-container>
<br>
<small>{{c.subTitle}}</small>
</td>
</tr>
</tbody>
</table>
</ng-container>
</div>
<ng-template #cipherAddEdit></ng-template>

View File

@@ -0,0 +1,94 @@
import {
Component,
ComponentFactoryResolver,
OnInit,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ApiService } from 'jslib/abstractions/api.service';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { CipherData } from 'jslib/models/data';
import { Cipher, SymmetricCryptoKey } from 'jslib/models/domain';
import { EmergencyAccessViewResponse } from 'jslib/models/response/emergencyAccessResponse';
import { CipherView } from 'jslib/models/view/cipherView';
import { ModalComponent } from '../modal.component';
import { EmergencyAddEditComponent } from './emergency-add-edit.component';
@Component({
selector: 'emergency-access-view',
templateUrl: 'emergency-access-view.component.html',
})
export class EmergencyAccessViewComponent implements OnInit {
@ViewChild('cipherAddEdit', { read: ViewContainerRef, static: true }) cipherAddEditModalRef: ViewContainerRef;
id: string;
ciphers: CipherView[] = [];
private modal: ModalComponent = null;
constructor(private cipherService: CipherService, private cryptoService: CryptoService,
private componentFactoryResolver: ComponentFactoryResolver, private router: Router,
private route: ActivatedRoute, private apiService: ApiService) { }
ngOnInit() {
this.route.params.subscribe((qParams) => {
if (qParams.id == null) {
return this.router.navigate(['settings/emergency-access']);
}
this.id = qParams.id;
this.load();
});
}
selectCipher(cipher: CipherView) {
if (this.modal != null) {
this.modal.close();
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = this.cipherAddEditModalRef.createComponent(factory).instance;
const childComponent = this.modal.show<EmergencyAddEditComponent>(EmergencyAddEditComponent, this.cipherAddEditModalRef);
childComponent.cipherId = cipher == null ? null : cipher.id;
childComponent.cipher = cipher;
this.modal.onClosed.subscribe(() => {
this.modal = null;
});
return childComponent;
}
async load() {
const response = await this.apiService.postEmergencyAccessView(this.id);
this.ciphers = await this.getAllCiphers(response);
}
protected async getAllCiphers(response: EmergencyAccessViewResponse): Promise<CipherView[]> {
const ciphers = response.ciphers;
const decCiphers: CipherView[] = [];
const oldKeyBuffer = await this.cryptoService.rsaDecrypt(response.keyEncrypted);
const oldEncKey = new SymmetricCryptoKey(oldKeyBuffer);
const promises: any[] = [];
ciphers.forEach((cipherResponse) => {
const cipherData = new CipherData(cipherResponse);
const cipher = new Cipher(cipherData);
promises.push(cipher.decrypt(oldEncKey).then((c) => decCiphers.push(c)));
});
await Promise.all(promises);
decCiphers.sort(this.cipherService.getLocaleSortingFunction());
return decCiphers;
}
}

View File

@@ -0,0 +1,159 @@
<div class="page-header">
<h1>{{'emergencyAccess' | i18n}}</h1>
</div>
<p>
{{'emergencyAccessDesc' | i18n}}
<a href="https://help.bitwarden.com/article/fingerprint-phrase/" target="_blank" rel="noopener">
{{'learnMore' | i18n}}.
</a>
</p>
<div class="page-header d-flex">
<h2>
{{'trustedEmergencyContacts' | i18n}}
<a href="#" appStopClick class="badge badge-primary" *ngIf="!canAccessPremium" (click)="premiumRequired()">
{{'premium' | i18n}}
</a>
</h2>
<div class="ml-auto d-flex">
<button class="btn btn-sm btn-outline-primary ml-3" type="button" (click)="invite()" [disabled]="!canAccessPremium">
<i aria-hidden="true" class="fa fa-plus fa-fw"></i>
{{'addEmergencyContact' |i18n}}
</button>
</div>
</div>
<table class="table table-hover table-list mb-0" *ngIf="trustedContacts && trustedContacts.length">
<tbody>
<tr *ngFor="let c of trustedContacts; let i = index">
<td width="30">
<app-avatar [data]="c.name || c.email" [email]="c.email" size="25" [circle]="true"
[fontSize]="14"></app-avatar>
</td>
<td>
<a href="#" appStopClick (click)="edit(c)">{{c.email}}</a>
<span class="badge badge-secondary"
*ngIf="c.status === emergencyAccessStatusType.Invited">{{'invited' | i18n}}</span>
<span class="badge badge-warning"
*ngIf="c.status === emergencyAccessStatusType.Accepted">{{'accepted' | i18n}}</span>
<span class="badge badge-warning"
*ngIf="c.status === emergencyAccessStatusType.RecoveryInitiated">{{'emergencyAccessRecoveryInitiated' | i18n}}</span>
<span class="badge badge-success"
*ngIf="c.status === emergencyAccessStatusType.RecoveryApproved">{{'emergencyAccessRecoveryApproved' | i18n}}</span>
<span class="badge badge-primary"
*ngIf="c.type === emergencyAccessType.View">{{'view' | i18n}}</span>
<span class="badge badge-primary"
*ngIf="c.type === emergencyAccessType.Takeover">{{'takeover' | i18n}}</span>
<small class="text-muted d-block" *ngIf="c.name">{{c.name}}</small>
</td>
<td class="table-list-options">
<div class="dropdown" appListDropdown>
<button class="btn btn-outline-secondary dropdown-toggle" type="button"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
appA11yTitle="{{'options' | i18n}}">
<i class="fa fa-cog fa-lg" aria-hidden="true"></i>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" href="#" appStopClick (click)="reinvite(c)"
*ngIf="c.status === emergencyAccessStatusType.Invited">
<i class="fa fa-fw fa-envelope-o" aria-hidden="true"></i>
{{'resendInvitation' | i18n}}
</a>
<a class="dropdown-item text-success" href="#" appStopClick (click)="confirm(c)"
*ngIf="c.status === emergencyAccessStatusType.Accepted">
<i class="fa fa-fw fa-check" aria-hidden="true"></i>
{{'confirm' | i18n}}
</a>
<a class="dropdown-item text-success" href="#" appStopClick (click)="approve(c)"
*ngIf="c.status === emergencyAccessStatusType.RecoveryInitiated">
<i class="fa fa-fw fa-check" aria-hidden="true"></i>
{{'approve' | i18n}}
</a>
<a class="dropdown-item text-warning" href="#" appStopClick (click)="reject(c)"
*ngIf="c.status === emergencyAccessStatusType.RecoveryInitiated || c.status === emergencyAccessStatusType.RecoveryApproved">
<i class="fa fa-fw fa-remove" aria-hidden="true"></i>
{{'reject' | i18n}}
</a>
<a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(c)">
<i class="fa fa-fw fa-remove" aria-hidden="true"></i>
{{'remove' | i18n}}
</a>
</div>
</div>
</td>
</tr>
</tbody>
</table>
<p *ngIf="!trustedContacts || !trustedContacts.length">{{'noTrustedContacts' | i18n}}</p>
<div class="page-header spaced-header">
<h2>{{'designatedEmergencyContacts' | i18n}}</h2>
</div>
<table class="table table-hover table-list mb-0" *ngIf="grantedContacts && grantedContacts.length">
<tbody>
<tr *ngFor="let c of grantedContacts; let i = index">
<td width="30">
<app-avatar [data]="c.name || c.email" [email]="c.email" size="25" [circle]="true"
[fontSize]="14"></app-avatar>
</td>
<td>
<span>{{c.email}}</span>
<span class="badge badge-secondary"
*ngIf="c.status === emergencyAccessStatusType.Invited">{{'invited' | i18n}}</span>
<span class="badge badge-warning"
*ngIf="c.status === emergencyAccessStatusType.Accepted">{{'accepted' | i18n}}</span>
<span class="badge badge-warning"
*ngIf="c.status === emergencyAccessStatusType.RecoveryInitiated">{{'emergencyAccessRecoveryInitiated' | i18n}}</span>
<span class="badge badge-success"
*ngIf="c.status === emergencyAccessStatusType.RecoveryApproved">{{'emergencyAccessRecoveryApproved' | i18n}}</span>
<span class="badge badge-primary"
*ngIf="c.type === emergencyAccessType.View">{{'view' | i18n}}</span>
<span class="badge badge-primary"
*ngIf="c.type === emergencyAccessType.Takeover">{{'takeover' | i18n}}</span>
<small class="text-muted d-block" *ngIf="c.name">{{c.name}}</small>
</td>
<td class="table-list-options">
<div class="dropdown" appListDropdown>
<button class="btn btn-outline-secondary dropdown-toggle" type="button"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
appA11yTitle="{{'options' | i18n}}">
<i class="fa fa-cog fa-lg" aria-hidden="true"></i>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" href="#" appStopClick (click)="requestAccess(c)"
*ngIf="c.status === emergencyAccessStatusType.Confirmed">
<i class="fa fa-fw fa-envelope-o" aria-hidden="true"></i>
{{'requestAccess' | i18n}}
</a>
<a class="dropdown-item" href="#" appStopClick (click)="takeover(c)"
*ngIf="c.status === emergencyAccessStatusType.RecoveryApproved && c.type === emergencyAccessType.Takeover">
<i class="fa fa-fw fa-key" aria-hidden="true"></i>
{{'takeover' | i18n}}
</a>
<a class="dropdown-item" [routerLink]="c.id"
*ngIf="c.status === emergencyAccessStatusType.RecoveryApproved && c.type === emergencyAccessType.View">
<i class="fa fa-fw fa-eye" aria-hidden="true"></i>
{{'view' | i18n}}
</a>
<a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(c)">
<i class="fa fa-fw fa-remove" aria-hidden="true"></i>
{{'remove' | i18n}}
</a>
</div>
</div>
</td>
</tr>
</tbody>
</table>
<p *ngIf="!grantedContacts || !grantedContacts.length">{{'noGrantedAccess' | i18n}}</p>
<ng-template #addEdit></ng-template>
<ng-template #takeoverTemplate></ng-template>
<ng-template #confirmTemplate></ng-template>

View File

@@ -0,0 +1,276 @@
import { Component, ComponentFactoryResolver, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { UserService } from 'jslib/abstractions/user.service';
import { EmergencyAccessStatusType } from 'jslib/enums/emergencyAccessStatusType';
import { EmergencyAccessType } from 'jslib/enums/emergencyAccessType';
import { Utils } from 'jslib/misc/utils';
import { EmergencyAccessConfirmRequest } from 'jslib/models/request/emergencyAccessConfirmRequest';
import { EmergencyAccessGranteeDetailsResponse, EmergencyAccessGrantorDetailsResponse } from 'jslib/models/response/emergencyAccessResponse';
import { ConstantsService } from 'jslib/services/constants.service';
import { ModalComponent } from '../modal.component';
import { EmergencyAccessAddEditComponent } from './emergency-access-add-edit.component';
import { EmergencyAccessConfirmComponent } from './emergency-access-confirm.component';
import { EmergencyAccessTakeoverComponent } from './emergency-access-takeover.component';
@Component({
selector: 'emergency-access',
templateUrl: 'emergency-access.component.html',
})
export class EmergencyAccessComponent implements OnInit {
@ViewChild('addEdit', { read: ViewContainerRef, static: true }) addEditModalRef: ViewContainerRef;
@ViewChild('takeoverTemplate', { read: ViewContainerRef, static: true}) takeoverModalRef: ViewContainerRef;
@ViewChild('confirmTemplate', { read: ViewContainerRef, static: true }) confirmModalRef: ViewContainerRef;
canAccessPremium: boolean;
trustedContacts: EmergencyAccessGranteeDetailsResponse[];
grantedContacts: EmergencyAccessGrantorDetailsResponse[];
emergencyAccessType = EmergencyAccessType;
emergencyAccessStatusType = EmergencyAccessStatusType;
actionPromise: Promise<any>;
private modal: ModalComponent = null;
constructor(private apiService: ApiService, private i18nService: I18nService,
private componentFactoryResolver: ComponentFactoryResolver,
private platformUtilsService: PlatformUtilsService,
private toasterService: ToasterService, private cryptoService: CryptoService,
private storageService: StorageService, private userService: UserService,
private messagingService: MessagingService) { }
async ngOnInit() {
this.canAccessPremium = await this.userService.canAccessPremium();
this.load();
}
async load() {
this.trustedContacts = (await this.apiService.getEmergencyAccessTrusted()).data;
this.grantedContacts = (await this.apiService.getEmergencyAccessGranted()).data;
}
async premiumRequired() {
if (!this.canAccessPremium) {
this.messagingService.send('premiumRequired');
return;
}
}
edit(details: EmergencyAccessGranteeDetailsResponse) {
if (this.modal != null) {
this.modal.close();
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = this.addEditModalRef.createComponent(factory).instance;
const childComponent = this.modal.show<EmergencyAccessAddEditComponent>(
EmergencyAccessAddEditComponent, this.addEditModalRef);
childComponent.name = details?.name ?? details?.email;
childComponent.emergencyAccessId = details?.id;
childComponent.readOnly = !this.canAccessPremium;
childComponent.onSaved.subscribe(() => {
this.modal.close();
this.load();
});
childComponent.onDeleted.subscribe(() => {
this.modal.close();
this.remove(details);
});
this.modal.onClosed.subscribe(() => {
this.modal = null;
});
}
invite() {
this.edit(null);
}
async reinvite(contact: EmergencyAccessGranteeDetailsResponse) {
if (this.actionPromise != null) {
return;
}
this.actionPromise = this.apiService.postEmergencyAccessReinvite(contact.id);
await this.actionPromise;
this.toasterService.popAsync('success', null, this.i18nService.t('hasBeenReinvited', contact.email));
this.actionPromise = null;
}
async confirm(contact: EmergencyAccessGranteeDetailsResponse) {
function updateUser() {
contact.status = EmergencyAccessStatusType.Confirmed;
}
if (this.actionPromise != null) {
return;
}
const autoConfirm = await this.storageService.get<boolean>(ConstantsService.autoConfirmFingerprints);
if (autoConfirm == null || !autoConfirm) {
if (this.modal != null) {
this.modal.close();
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = this.confirmModalRef.createComponent(factory).instance;
const childComponent = this.modal.show<EmergencyAccessConfirmComponent>(
EmergencyAccessConfirmComponent, this.confirmModalRef);
childComponent.name = contact?.name ?? contact?.email;
childComponent.emergencyAccessId = contact.id;
childComponent.userId = contact?.granteeId;
childComponent.onConfirmed.subscribe(async () => {
this.modal.close();
childComponent.formPromise = this.doConfirmation(contact);
await childComponent.formPromise;
updateUser();
this.toasterService.popAsync('success', null, this.i18nService.t('hasBeenConfirmed', contact.name || contact.email));
});
this.modal.onClosed.subscribe(() => {
this.modal = null;
});
return;
}
this.actionPromise = this.doConfirmation(contact);
await this.actionPromise;
updateUser();
this.toasterService.popAsync('success', null, this.i18nService.t('hasBeenConfirmed', contact.name || contact.email));
this.actionPromise = null;
}
async remove(details: EmergencyAccessGranteeDetailsResponse | EmergencyAccessGrantorDetailsResponse) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('removeUserConfirmation'), details.name || details.email,
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
try {
await this.apiService.deleteEmergencyAccess(details.id);
this.toasterService.popAsync('success', null, this.i18nService.t('removedUserId', details.name || details.email));
if (details instanceof EmergencyAccessGranteeDetailsResponse) {
this.removeGrantee(details);
} else {
this.removeGrantor(details);
}
} catch { }
}
async requestAccess(details: EmergencyAccessGrantorDetailsResponse) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('requestAccessConfirmation', details.waitTimeDays.toString()),
details.name || details.email,
this.i18nService.t('requestAccess'),
this.i18nService.t('no'),
'warning',
);
if (!confirmed) {
return false;
}
await this.apiService.postEmergencyAccessInitiate(details.id);
details.status = EmergencyAccessStatusType.RecoveryInitiated;
this.toasterService.popAsync('success', null, this.i18nService.t('requestSent', details.name || details.email));
}
async approve(details: EmergencyAccessGranteeDetailsResponse) {
const type = this.i18nService.t(details.type === EmergencyAccessType.View ? 'view' : 'takeover');
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('approveAccessConfirmation', details.name, type),
details.name || details.email,
this.i18nService.t('approve'),
this.i18nService.t('no'),
'warning',
);
if (!confirmed) {
return false;
}
await this.apiService.postEmergencyAccessApprove(details.id);
details.status = EmergencyAccessStatusType.RecoveryApproved;
this.toasterService.popAsync('success', null, this.i18nService.t('emergencyApproved', details.name || details.email));
}
async reject(details: EmergencyAccessGranteeDetailsResponse) {
await this.apiService.postEmergencyAccessReject(details.id);
details.status = EmergencyAccessStatusType.Confirmed;
this.toasterService.popAsync('success', null, this.i18nService.t('emergencyRejected', details.name || details.email));
}
async takeover(details: EmergencyAccessGrantorDetailsResponse) {
if (this.modal != null) {
this.modal.close();
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = this.addEditModalRef.createComponent(factory).instance;
const childComponent = this.modal.show<EmergencyAccessTakeoverComponent>(
EmergencyAccessTakeoverComponent, this.takeoverModalRef);
childComponent.name = details != null ? details.name || details.email : null;
childComponent.email = details.email;
childComponent.emergencyAccessId = details != null ? details.id : null;
childComponent.onDone.subscribe(() => {
this.modal.close();
this.toasterService.popAsync('success', null, this.i18nService.t('passwordResetFor', details.name || details.email));
});
this.modal.onClosed.subscribe(() => {
this.modal = null;
});
}
private removeGrantee(details: EmergencyAccessGranteeDetailsResponse) {
const index = this.trustedContacts.indexOf(details);
if (index > -1) {
this.trustedContacts.splice(index, 1);
}
}
private removeGrantor(details: EmergencyAccessGrantorDetailsResponse) {
const index = this.grantedContacts.indexOf(details);
if (index > -1) {
this.grantedContacts.splice(index, 1);
}
}
// Encrypt the master password hash using the grantees public key, and send it to bitwarden for escrow.
private async doConfirmation(details: EmergencyAccessGranteeDetailsResponse) {
const encKey = await this.cryptoService.getEncKey();
const publicKeyResponse = await this.apiService.getUserPublicKey(details.granteeId);
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
try {
// tslint:disable-next-line
console.log('User\'s fingerprint: ' +
(await this.cryptoService.getFingerprint(details.granteeId, publicKey.buffer)).join('-'));
} catch { }
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
const request = new EmergencyAccessConfirmRequest();
request.key = encryptedKey.encryptedString;
await this.apiService.postEmergencyAccessConfirm(details.id, request);
}
}

View File

@@ -0,0 +1,47 @@
import { Component } from '@angular/core';
import { AuditService } from 'jslib/abstractions/audit.service';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { CollectionService } from 'jslib/abstractions/collection.service';
import { EventService } from 'jslib/abstractions/event.service';
import { FolderService } from 'jslib/abstractions/folder.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { PolicyService } from 'jslib/abstractions/policy.service';
import { StateService } from 'jslib/abstractions/state.service';
import { TotpService } from 'jslib/abstractions/totp.service';
import { UserService } from 'jslib/abstractions/user.service';
import { Cipher } from 'jslib/models/domain/cipher';
import { AddEditComponent as BaseAddEditComponent } from '../vault/add-edit.component';
@Component({
selector: 'app-org-vault-add-edit',
templateUrl: '../vault/add-edit.component.html',
})
export class EmergencyAddEditComponent extends BaseAddEditComponent {
originalCipher: Cipher = null;
viewOnly = true;
constructor(cipherService: CipherService, folderService: FolderService,
i18nService: I18nService, platformUtilsService: PlatformUtilsService,
auditService: AuditService, stateService: StateService,
userService: UserService, collectionService: CollectionService,
totpService: TotpService, passwordGenerationService: PasswordGenerationService,
messagingService: MessagingService, eventService: EventService, policyService: PolicyService) {
super(cipherService, folderService, i18nService, platformUtilsService, auditService, stateService,
userService, collectionService, totpService, passwordGenerationService, messagingService,
eventService, policyService);
}
async load() {
this.title = this.i18nService.t('viewItem');
}
protected async loadCipher() {
return Promise.resolve(this.originalCipher);
}
}

View File

@@ -24,7 +24,7 @@ import { Organization } from 'jslib/models/domain/organization';
})
export class LinkSsoComponent extends SsoComponent implements AfterContentInit {
@Input() organization: Organization;
returnUri: string = '/settings/organizations'
returnUri: string = '/settings/organizations';
constructor(platformUtilsService: PlatformUtilsService, i18nService: I18nService,
apiService: ApiService, authService: AuthService,

View File

@@ -54,6 +54,9 @@
<small *ngIf="selectableProduct.hasSelfHost">• {{'onPremHostingOptional' | i18n}}</small>
<small *ngIf="selectableProduct.hasSso">• {{'includeSsoAuthentication' | i18n}}</small>
<small *ngIf="selectableProduct.hasPolicies">• {{'includeEnterprisePolicies' | i18n}}</small>
<small *ngIf="selectableProduct.trialPeriodDays">
{{'xDayFreeTrial' | i18n : selectableProduct.trialPeriodDays }}
</small>
</ng-container>
<ng-template #fullFeatureList>
<small *ngIf="selectableProduct.product == productTypes.Free">
@@ -206,21 +209,27 @@
</label>
</div>
<hr class="my-3">
<div class="text-lg">
<strong>{{'total' | i18n}}:</strong> {{subtotal | currency:'USD $'}} /{{selectedPlanInterval | i18n}}
<h2 class="spaced-header mb-4">{{ (createOrganization ? 'paymentInformation' : 'billingInformation') | i18n}}</h2>
<app-payment *ngIf="createOrganization" [hideCredit]="true"></app-payment>
<app-tax-info (onCountryChanged)="changedCountry()"></app-tax-info>
<div id="price" class="my-4">
<div class="text-muted text-sm">
{{ 'planPrice' | i18n }}: {{ subtotal | currency: 'USD $' }}
<br />
<ng-container>
{{ 'estimatedTax' | i18n }}: {{ taxCharges | currency: 'USD $' }}
</ng-container>
</div>
<hr class="my-1 col-3 ml-0">
<p class="text-lg"><strong>{{'total' | i18n}}:</strong>
{{total | currency:'USD $'}}/{{selectedPlanInterval | i18n}}</p>
</div>
<ng-container *ngIf="createOrganization">
<small
class="text-muted font-italic">{{'paymentChargedWithTrial' | i18n : (selectedPlanInterval | i18n) }}</small>
<h2 class="spaced-header mb-4">{{'paymentInformation' | i18n}}</h2>
<app-payment [hideCredit]="true"></app-payment>
<app-tax-info (onCountryChanged)="changedCountry()"></app-tax-info>
</ng-container>
<small class="text-muted font-italic">{{'paymentChargedWithTrial' | i18n : (selectedPlanInterval | i18n) }}</small>
<ng-container *ngIf="!createOrganization">
<app-payment [showMethods]="false"></app-payment>
</ng-container>
<small class="text-muted font-italic mt-2 d-block" *ngIf="!createOrganization">
{{'paymentCharged' | i18n : (interval | i18n) }}</small>
{{'paymentCharged' | i18n : (selectedPlanInterval | i18n) }}</small>
</div>
<div *ngIf="singleOrgPolicyBlock" class="mt-4">
<app-callout [type]="'error'">{{'singleOrgBlockCreateMessage' | i18n}}</app-callout>

View File

@@ -74,6 +74,9 @@ export class OrganizationPlansComponent implements OnInit {
if (!this.selfHosted) {
const plans = await this.apiService.getPlans();
this.plans = plans.data;
if (this.product === ProductType.Enterprise || this.product === ProductType.Teams) {
this.ownedBusiness = true;
}
}
this.loading = false;
}
@@ -159,6 +162,16 @@ export class OrganizationPlansComponent implements OnInit {
return subTotal;
}
get taxCharges() {
return this.taxComponent != null && this.taxComponent.taxRate != null ?
(this.taxComponent.taxRate / 100) * this.subtotal :
0;
}
get total() {
return (this.subtotal + this.taxCharges) || 0;
}
changedProduct() {
this.plan = this.selectablePlans[0].type;
if (!this.selectedPlan.hasPremiumAccessOption) {
@@ -179,7 +192,8 @@ export class OrganizationPlansComponent implements OnInit {
if (!this.ownedBusiness || this.selectedPlan.canBeUsedByBusiness) {
return;
}
this.plan = PlanType.TeamsMonthly;
this.product = ProductType.Teams;
this.plan = PlanType.TeamsAnnually;
}
changedCountry() {
@@ -278,6 +292,9 @@ export class OrganizationPlansComponent implements OnInit {
request.premiumAccessAddon = this.selectedPlan.hasPremiumAccessOption &&
this.premiumAccessAddon;
request.planType = this.selectedPlan.type;
request.billingAddressCountry = this.taxComponent.taxInfo.country;
request.billingAddressPostalCode = this.taxComponent.taxInfo.postalCode;
const result = await this.apiService.postOrganizationUpgrade(this.organizationId, request);
if (!result.success && result.paymentIntentClientSecret != null) {
await this.paymentComponent.handleStripeCardPayment(result.paymentIntentClientSecret, null);

View File

@@ -69,13 +69,22 @@
<br> {{'additionalStorageGb' | i18n}}: {{additionalStorage || 0}} GB &times; {{storageGbPrice | currency:'$'}} = {{additionalStorageTotal
| currency:'$'}}
<hr class="my-3">
<div class="text-lg">
<strong>{{'total' | i18n}}:</strong> {{total | currency:'USD $'}} /{{'year' | i18n}}
</div>
<small class="text-muted font-italic">{{'paymentChargedAnnually' | i18n}}</small>
<h2 class="spaced-header mb-4">{{'paymentInformation' | i18n}}</h2>
<app-payment [hideBank]="true"></app-payment>
<app-tax-info></app-tax-info>
<div id="price" class="my-4">
<div class="text-muted text-sm">
{{ 'planPrice' | i18n }}: {{ subtotal | currency: 'USD $' }}
<br />
<ng-container>
{{ 'estimatedTax' | i18n }}: {{ taxCharges | currency: 'USD $' }}
</ng-container>
</div>
<hr class="my-1 col-3 ml-0">
<p class="text-lg"><strong>{{'total' | i18n}}:</strong>
{{total | currency:'USD $'}}/{{'year' | i18n}}</p>
</div>
<small class="text-muted font-italic">{{'paymentChargedAnnually' | i18n}}</small>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'submit' | i18n}}</span>

View File

@@ -114,7 +114,17 @@ export class PremiumComponent implements OnInit {
return this.storageGbPrice * Math.abs(this.additionalStorage || 0);
}
get subtotal(): number {
return this.premiumPrice + this.additionalStorageTotal;
}
get taxCharges(): number {
return this.taxInfoComponent != null && this.taxInfoComponent.taxRate != null ?
(this.taxInfoComponent.taxRate / 100) * this.subtotal :
0;
}
get total(): number {
return this.additionalStorageTotal + this.premiumPrice;
return (this.subtotal + this.taxCharges) || 0;
}
}

View File

@@ -28,6 +28,9 @@
<a routerLink="domain-rules" class="list-group-item" routerLinkActive="active">
{{'domainRules' | i18n}}
</a>
<a routerLink="emergency-access" class="list-group-item" routerLinkActive="active">
{{'emergencyAccess' | i18n}}
</a>
</div>
</div>
</div>

View File

@@ -7,6 +7,7 @@ import { ActivatedRoute } from '@angular/router';
import { ApiService } from 'jslib/abstractions/api.service';
import { OrganizationTaxInfoUpdateRequest } from 'jslib/models/request/organizationTaxInfoUpdateRequest';
import { TaxInfoUpdateRequest } from 'jslib/models/request/taxInfoUpdateRequest';
import { TaxRateResponse } from 'jslib/models/response/taxRateResponse';
@Component({
selector: 'app-tax-info',
@@ -28,6 +29,8 @@ export class TaxInfoComponent {
includeTaxId: false,
};
taxRates: TaxRateResponse[];
private pristine: any = {
taxId: null,
line1: null,
@@ -77,9 +80,22 @@ export class TaxInfoComponent {
this.onCountryChanged.emit();
}
});
const taxRates = await this.apiService.getTaxRates();
this.taxRates = taxRates.data;
this.loading = false;
}
get taxRate() {
if (this.taxRates != null) {
const localTaxRate = this.taxRates.find(x =>
x.country === this.taxInfo.country &&
x.postalCode === this.taxInfo.postalCode
);
return localTaxRate?.rate ?? null;
}
}
getTaxInfoRequest(): TaxInfoUpdateRequest {
if (this.organizationId) {
const request = new OrganizationTaxInfoUpdateRequest();

View File

@@ -30,8 +30,8 @@ export class TwoFactorRecoveryComponent {
'<code style="font-family: Menlo, Monaco, Consolas, \'Courier New\', monospace;">' +
this.code + '</code></div>' +
'<p style="text-align: center;">' + new Date() + '</p>');
w.onafterprint = () => w.close();
w.print();
w.close();
}
private formatString(s: string) {

View File

@@ -3,13 +3,13 @@
<h1>{{'exportVault' | i18n}}</h1>
</div>
<p>{{'exportMasterPassword' | i18n}}</p>
<app-callout type="warning">{{'exportWarningDesc' | i18n}}</app-callout>
<div class="row">
<div class="form-group col-6">
<label for="format">{{'fileFormat' | i18n}}</label>
<select class="form-control" id="format" name="Format" [(ngModel)]="format">
<option value="json">.json</option>
<option value="csv">.csv</option>
<option value="encrypted_json">.json (Encrypted)</option>
</select>
</div>
</div>

View File

@@ -13,6 +13,8 @@ import { ExportComponent as BaseExportComponent } from 'jslib/angular/components
templateUrl: 'export.component.html',
})
export class ExportComponent extends BaseExportComponent {
organizationId: string;
constructor(cryptoService: CryptoService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, exportService: ExportService,
eventService: EventService) {

View File

@@ -21,7 +21,12 @@
<app-vault-icon [cipher]="c"></app-vault-icon>
</td>
<td class="reduced-lh wrap">
<a href="#" appStopClick (click)="selectCipher(c)" title="{{'editItem' | i18n}}">{{c.name}}</a>
<ng-container *ngIf="!organization || canManageCipher(c) ; else cantManage">
<a href="#" appStopClick (click)="selectCipher(c)" title="{{'editItem' | i18n}}">{{c.name}}</a>
</ng-container>
<ng-template #cantManage>
<span>{{c.name}}</span>
</ng-template>
<ng-container *ngIf="!organization && c.organizationId">
<i class="fa fa-share-alt" appStopProp title="{{'shared' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'shared' | i18n}}</span>

View File

@@ -61,4 +61,9 @@ export class ExposedPasswordsReportComponent extends CipherReportComponent imple
protected getAllCiphers(): Promise<CipherView[]> {
return this.cipherService.getAllDecrypted();
}
protected canManageCipher(c: CipherView): boolean {
// this will only ever be false from the org view;
return true;
}
}

View File

@@ -77,12 +77,11 @@
https://help.bitwarden.com/article/import-from-chrome/</a>
</ng-container>
<ng-container *ngIf="format === 'firefoxcsv'">
Use the
<a target="_blank" rel="noopener"
href="https://github.com/kspearrin/ff-password-exporter/blob/master/README.md#ff-password-exporter">
FF Password Exporter</a> application to export your passwords to a CSV file.
See detailed instructions on our help site at
<a target="_blank" rel="noopener" href="https://bitwarden.com/help/article/import-from-firefox/">
https://bitwarden.com/help/article/import-from-firefox/</a>.
</ng-container>
<ng-container *ngIf="format === '1password1pif' || format === '1passwordwincsv'">
<ng-container *ngIf="format === '1password1pif' || format === '1passwordwincsv' || format === '1passwordmaccsv'">
See detailed instructions on our help site at
<a target="_blank" rel="noopener" href="https://help.bitwarden.com/article/import-from-1password/">
https://help.bitwarden.com/article/import-from-1password/</a>.

View File

@@ -47,7 +47,7 @@ export class ImportComponent implements OnInit {
}
async submit() {
const importer = this.importService.getImporter(this.format, this.organizationId != null);
const importer = this.importService.getImporter(this.format, this.organizationId);
if (importer === null) {
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('selectFormat'));

View File

@@ -27,7 +27,12 @@
<app-vault-icon [cipher]="c"></app-vault-icon>
</td>
<td class="reduced-lh wrap">
<a href="#" appStopClick (click)="selectCipher(c)" title="{{'editItem' | i18n}}">{{c.name}}</a>
<ng-container *ngIf="!organization || canManageCipher(c) ; else cantManage">
<a href="#" appStopClick (click)="selectCipher(c)" title="{{'editItem' | i18n}}">{{c.name}}</a>
</ng-container>
<ng-template #cantManage>
<span>{{c.name}}</span>
</ng-template>
<ng-container *ngIf="!organization && c.organizationId">
<i class="fa fa-share-alt" appStopProp title="{{'shared' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'shared' | i18n}}</span>

View File

@@ -55,4 +55,9 @@ export class ReusedPasswordsReportComponent extends CipherReportComponent implem
protected getAllCiphers(): Promise<CipherView[]> {
return this.cipherService.getAllDecrypted();
}
protected canManageCipher(c: CipherView): boolean {
// this will only ever be false from an organization view
return true;
}
}

View File

@@ -27,7 +27,12 @@
<app-vault-icon [cipher]="c"></app-vault-icon>
</td>
<td class="reduced-lh wrap">
<a href="#" appStopClick (click)="selectCipher(c)" title="{{'editItem' | i18n}}">{{c.name}}</a>
<ng-container *ngIf="!organization || canManageCipher(c) ; else cantManage">
<a href="#" appStopClick (click)="selectCipher(c)" title="{{'editItem' | i18n}}">{{c.name}}</a>
</ng-container>
<ng-template #cantManage>
<span>{{c.name}}</span>
</ng-template>
<ng-container *ngIf="!organization && c.organizationId">
<i class="fa fa-share-alt" appStopProp title="{{'shared' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'shared' | i18n}}</span>

View File

@@ -75,6 +75,11 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen
return this.cipherService.getAllDecrypted();
}
protected canManageCipher(c: CipherView): boolean {
// this will only ever be false from the org view;
return true;
}
private scoreKey(score: number): [string, string] {
switch (score) {
case 4:

View File

@@ -9,7 +9,10 @@
</button>
</div>
<div class="modal-body" *ngIf="cipher">
<div class="row" *ngIf="!editMode">
<app-callout type="info" *ngIf="allowOwnershipAssignment() && !allowPersonal">
{{'personalOwnershipPolicyInEffect' | i18n}}
</app-callout>
<div class="row" *ngIf="!editMode && !viewOnly">
<div class="col-6 form-group">
<label for="type">{{'whatTypeOfItem' | i18n}}</label>
<select id="type" name="Type" [(ngModel)]="cipher.type" class="form-control"
@@ -21,13 +24,13 @@
<div class="row">
<div class="col-6 form-group">
<label for="name">{{'name' | i18n}}</label>
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="cipher.name"
required [disabled]="cipher.isDeleted">
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="cipher.name" required
[disabled]="cipher.isDeleted || viewOnly">
</div>
<div class="col-6 form-group" *ngIf="!organization">
<label for="folder">{{'folder' | i18n}}</label>
<select id="folder" name="FolderId" [(ngModel)]="cipher.folderId" class="form-control"
[disabled]="cipher.isDeleted">
<select id="folder" name="FolderId" [(ngModel)]="cipher.folderId" class="form-control"
[disabled]="cipher.isDeleted || viewOnly">
<option *ngFor="let f of folders" [ngValue]="f.id">{{f.name}}</option>
</select>
</div>
@@ -39,7 +42,8 @@
<label for="loginUsername">{{'username' | i18n}}</label>
<div class="input-group">
<input id="loginUsername" class="form-control" type="text" name="Login.Username"
[(ngModel)]="cipher.login.username" appInputVerbatim [disabled]="cipher.isDeleted">
[(ngModel)]="cipher.login.username" appInputVerbatim
[disabled]="cipher.isDeleted || viewOnly">
<div class="input-group-append" *ngIf="!cipher.isDeleted">
<button type="button" class="btn btn-outline-secondary"
appA11yTitle="{{'copyUsername' | i18n}}"
@@ -52,7 +56,7 @@
<div class="col-6 form-group">
<div class="d-flex">
<label for="loginPassword">{{'password' | i18n}}</label>
<div class="ml-auto d-flex" *ngIf="!cipher.isDeleted">
<div class="ml-auto d-flex" *ngIf="!cipher.isDeleted && !viewOnly">
<a href="#" class="d-block mr-2" appStopClick
appA11yTitle="{{'generatePassword' | i18n}}" (click)="generatePassword()"
*ngIf="cipher.viewPassword">
@@ -72,7 +76,7 @@
<input id="loginPassword" class="form-control text-monospace"
type="{{showPassword ? 'text' : 'password'}}" name="Login.Password"
[(ngModel)]="cipher.login.password" appInputVerbatim autocomplete="new-password"
[disabled]="cipher.isDeleted || !cipher.viewPassword">
[disabled]="cipher.isDeleted || !cipher.viewPassword || viewOnly">
<div class="input-group-append">
<button type="button" class="btn btn-outline-secondary"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()"
@@ -93,8 +97,9 @@
<div class="row">
<div class="col-6 form-group">
<label for="loginTotp">{{'authenticatorKeyTotp' | i18n}}</label>
<input id="loginTotp" type="{{cipher.viewPassword ? 'text' : 'password'}}" name="Login.Totp" class="form-control text-monospace"
[(ngModel)]="cipher.login.totp" appInputVerbatim [disabled]="cipher.isDeleted || !cipher.viewPassword">
<input id="loginTotp" type="{{cipher.viewPassword ? 'text' : 'password'}}" name="Login.Totp"
class="form-control text-monospace" [(ngModel)]="cipher.login.totp" appInputVerbatim
[disabled]="cipher.isDeleted || !cipher.viewPassword || viewOnly">
</div>
<div class="col-6 form-group totp d-flex align-items-end" [ngClass]="{'low': totpLow}">
<div *ngIf="!cipher.login.totp || !totpCode">
@@ -137,7 +142,8 @@
<label for="loginUri{{i}}">{{'uriPosition' | i18n : (i + 1)}}</label>
<div class="input-group">
<input class="form-control" id="loginUri{{i}}" type="text"
name="Login.Uris[{{i}}].Uri" [(ngModel)]="u.uri" [disabled]="cipher.isDeleted"
name="Login.Uris[{{i}}].Uri" [(ngModel)]="u.uri"
[disabled]="cipher.isDeleted || viewOnly"
placeholder="{{'ex' | i18n}} https://google.com" appInputVerbatim>
<div class="input-group-append">
<button type="button" class="btn btn-outline-secondary"
@@ -164,21 +170,22 @@
</a>
</div>
<div class="d-flex">
<select class="form-control" id="loginUriMatch{{i}}" name="Login.Uris[{{i}}].Match"
[(ngModel)]="u.match" (change)="loginUriMatchChanged(u)"
[disabled]="cipher.isDeleted">
<select class="form-control overflow-hidden" id="loginUriMatch{{i}}"
name="Login.Uris[{{i}}].Match" [(ngModel)]="u.match"
(change)="loginUriMatchChanged(u)" [disabled]="cipher.isDeleted || viewOnly">
<option *ngFor="let o of uriMatchOptions" [ngValue]="o.value">{{o.name}}
</option>
</select>
<button type="button" class="btn btn-link text-danger ml-2" (click)="removeUri(u)"
appA11yTitle="{{'remove' | i18n}}" *ngIf="!cipher.isDeleted">
appA11yTitle="{{'remove' | i18n}}" *ngIf="!cipher.isDeleted && !viewOnly">
<i class="fa fa-minus-circle fa-lg" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
</ng-container>
<a href="#" appStopClick (click)="addUri()" class="d-inline-block mb-3" *ngIf="!cipher.isDeleted">
<a href="#" appStopClick (click)="addUri()" class="d-inline-block mb-3"
*ngIf="!cipher.isDeleted && !viewOnly">
<i class="fa fa-plus-circle fa-fw" aria-hidden="true"></i> {{'newUri' | i18n}}
</a>
</ng-container>
@@ -189,12 +196,12 @@
<label for="cardCardholderName">{{'cardholderName' | i18n}}</label>
<input id="cardCardholderName" class="form-control" type="text"
name="Card.CardCardholderName" [(ngModel)]="cipher.card.cardholderName"
[disabled]="cipher.isDeleted">
[disabled]="cipher.isDeleted || viewOnly">
</div>
<div class="col-6 form-group">
<label for="cardBrand">{{'brand' | i18n}}</label>
<select id="cardBrand" class="form-control" name="Card.Brand"
[(ngModel)]="cipher.card.brand" [disabled]="cipher.isDeleted">
[(ngModel)]="cipher.card.brand" [disabled]="cipher.isDeleted || viewOnly">
<option *ngFor="let o of cardBrandOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
@@ -204,7 +211,8 @@
<label for="cardNumber">{{'number' | i18n}}</label>
<div class="input-group">
<input id="cardNumber" class="form-control" type="text" name="Card.Number"
[(ngModel)]="cipher.card.number" appInputVerbatim [disabled]="cipher.isDeleted">
[(ngModel)]="cipher.card.number" appInputVerbatim
[disabled]="cipher.isDeleted || viewOnly">
<div class="input-group-append">
<button type="button" class="btn btn-outline-secondary"
appA11yTitle="{{'copyNumber' | i18n}}"
@@ -217,15 +225,15 @@
<div class="col form-group">
<label for="cardExpMonth">{{'expirationMonth' | i18n}}</label>
<select id="cardExpMonth" class="form-control" name="Card.ExpMonth"
[(ngModel)]="cipher.card.expMonth" [disabled]="cipher.isDeleted">
[(ngModel)]="cipher.card.expMonth" [disabled]="cipher.isDeleted || viewOnly">
<option *ngFor="let o of cardExpMonthOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
<div class="col form-group">
<label for="cardExpYear">{{'expirationYear' | i18n}}</label>
<input id="cardExpYear" class="form-control" type="text" name="Card.ExpYear"
[(ngModel)]="cipher.card.expYear" placeholder="{{'ex' | i18n}} 2019"
[disabled]="cipher.isDeleted">
[(ngModel)]="cipher.card.expYear" placeholder="{{'ex' | i18n}} 2019"
[disabled]="cipher.isDeleted || viewOnly">
</div>
</div>
<div class="row">
@@ -235,7 +243,7 @@
<input id="cardCode" class="form-control text-monospace"
type="{{showCardCode ? 'text' : 'password'}}" name="Card.Code"
[(ngModel)]="cipher.card.code" appInputVerbatim autocomplete="new-password"
[disabled]="cipher.isDeleted">
[disabled]="cipher.isDeleted || viewOnly">
<div class="input-group-append">
<button type="button" class="btn btn-outline-secondary"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleCardCode()"
@@ -259,7 +267,7 @@
<div class="col-4 form-group">
<label for="idTitle">{{'title' | i18n}}</label>
<select id="idTitle" class="form-control" name="Identity.Title"
[(ngModel)]="cipher.identity.title" [disabled]="cipher.isDeleted">
[(ngModel)]="cipher.identity.title" [disabled]="cipher.isDeleted || viewOnly">
<option *ngFor="let o of identityTitleOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
@@ -268,108 +276,113 @@
<div class="col-4 form-group">
<label for="idFirstName">{{'firstName' | i18n}}</label>
<input id="idFirstName" class="form-control" type="text" name="Identity.FirstName"
[(ngModel)]="cipher.identity.firstName" [disabled]="cipher.isDeleted">
[(ngModel)]="cipher.identity.firstName" [disabled]="cipher.isDeleted || viewOnly">
</div>
<div class="col-4 form-group">
<label for="idMiddleName">{{'middleName' | i18n}}</label>
<input id="idMiddleName" class="form-control" type="text" name="Identity.MiddleName"
[(ngModel)]="cipher.identity.middleName" [disabled]="cipher.isDeleted">
[(ngModel)]="cipher.identity.middleName" [disabled]="cipher.isDeleted || viewOnly">
</div>
<div class="col-4 form-group">
<label for="idLastName">{{'lastName' | i18n}}</label>
<input id="idLastName" class="form-control" type="text" name="Identity.LastName"
[(ngModel)]="cipher.identity.lastName" [disabled]="cipher.isDeleted">
[(ngModel)]="cipher.identity.lastName" [disabled]="cipher.isDeleted || viewOnly">
</div>
</div>
<div class="row">
<div class="col-4 form-group">
<label for="idUsername">{{'username' | i18n}}</label>
<input id="idUsername" class="form-control" type="text" name="Identity.Username"
[(ngModel)]="cipher.identity.username" appInputVerbatim [disabled]="cipher.isDeleted">
[(ngModel)]="cipher.identity.username" appInputVerbatim
[disabled]="cipher.isDeleted || viewOnly">
</div>
<div class="col-4 form-group">
<label for="idCompany">{{'company' | i18n}}</label>
<input id="idCompany" class="form-control" type="text" name="Identity.Company"
[(ngModel)]="cipher.identity.company" [disabled]="cipher.isDeleted">
[(ngModel)]="cipher.identity.company" [disabled]="cipher.isDeleted || viewOnly">
</div>
</div>
<div class="row">
<div class="col-4 form-group">
<label for="idSsn">{{'ssn' | i18n}}</label>
<input id="idSsn" class="form-control" type="text" name="Identity.SSN"
[(ngModel)]="cipher.identity.ssn" appInputVerbatim [disabled]="cipher.isDeleted">
[(ngModel)]="cipher.identity.ssn" appInputVerbatim
[disabled]="cipher.isDeleted || viewOnly">
</div>
<div class="col-4 form-group">
<label for="idPassportNumber">{{'passportNumber' | i18n}}</label>
<input id="idPassportNumber" class="form-control" type="text" name="Identity.PassportNumber"
[(ngModel)]="cipher.identity.passportNumber" appInputVerbatim [disabled]="cipher.isDeleted">
[(ngModel)]="cipher.identity.passportNumber" appInputVerbatim
[disabled]="cipher.isDeleted || viewOnly">
</div>
<div class="col-4 form-group">
<label for="idLicenseNumber">{{'licenseNumber' | i18n}}</label>
<input id="idLicenseNumber" class="form-control" type="text" name="Identity.LicenseNumber"
[(ngModel)]="cipher.identity.licenseNumber" appInputVerbatim [disabled]="cipher.isDeleted">
[(ngModel)]="cipher.identity.licenseNumber" appInputVerbatim
[disabled]="cipher.isDeleted || viewOnly">
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="idEmail">{{'email' | i18n}}</label>
<input id="idEmail" class="form-control" type="text" name="Identity.Email"
[(ngModel)]="cipher.identity.email" appInputVerbatim [disabled]="cipher.isDeleted">
[(ngModel)]="cipher.identity.email" appInputVerbatim
[disabled]="cipher.isDeleted || viewOnly">
</div>
<div class="col-6 form-group">
<label for="idPhone">{{'phone' | i18n}}</label>
<input id="idPhone" class="form-control" type="text" name="Identity.Phone"
[(ngModel)]="cipher.identity.phone" [disabled]="cipher.isDeleted">
[(ngModel)]="cipher.identity.phone" [disabled]="cipher.isDeleted || viewOnly">
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="idAddress1">{{'address1' | i18n}}</label>
<input id="idAddress1" class="form-control" type="text" name="Identity.Address1"
[(ngModel)]="cipher.identity.address1" [disabled]="cipher.isDeleted">
[(ngModel)]="cipher.identity.address1" [disabled]="cipher.isDeleted || viewOnly">
</div>
<div class="col-6 form-group">
<label for="idAddress2">{{'address2' | i18n}}</label>
<input id="idAddress2" class="form-control" type="text" name="Identity.Address2"
[(ngModel)]="cipher.identity.address2" [disabled]="cipher.isDeleted">
[(ngModel)]="cipher.identity.address2" [disabled]="cipher.isDeleted || viewOnly">
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="idAddress3">{{'address3' | i18n}}</label>
<input id="idAddress3" class="form-control" type="text" name="Identity.Address3"
[(ngModel)]="cipher.identity.address3" [disabled]="cipher.isDeleted">
[(ngModel)]="cipher.identity.address3" [disabled]="cipher.isDeleted || viewOnly">
</div>
<div class="col-6 form-group">
<label for="idCity">{{'cityTown' | i18n}}</label>
<input id="idCity" class="form-control" type="text" name="Identity.City"
[(ngModel)]="cipher.identity.city" [disabled]="cipher.isDeleted">
[(ngModel)]="cipher.identity.city" [disabled]="cipher.isDeleted || viewOnly">
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="idState">{{'stateProvince' | i18n}}</label>
<input id="idState" class="form-control" type="text" name="Identity.State"
[(ngModel)]="cipher.identity.state" [disabled]="cipher.isDeleted">
[(ngModel)]="cipher.identity.state" [disabled]="cipher.isDeleted || viewOnly">
</div>
<div class="col-6 form-group">
<label for="idPostalCode">{{'zipPostalCode' | i18n}}</label>
<input id="idPostalCode" class="form-control" type="text" name="Identity.PostalCode"
[(ngModel)]="cipher.identity.postalCode" [disabled]="cipher.isDeleted">
[(ngModel)]="cipher.identity.postalCode" [disabled]="cipher.isDeleted || viewOnly">
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="idCountry">{{'country' | i18n}}</label>
<input id="idCountry" class="form-control" type="text" name="Identity.Country"
[(ngModel)]="cipher.identity.country" [disabled]="cipher.isDeleted">
[(ngModel)]="cipher.identity.country" [disabled]="cipher.isDeleted || viewOnly">
</div>
</div>
</ng-container>
<div class="form-group">
<label for="notes">{{'notes' | i18n}}</label>
<textarea id="notes" name="Notes" rows="6" [(ngModel)]="cipher.notes" [disabled]="cipher.isDeleted"
class="form-control"></textarea>
<textarea id="notes" name="Notes" rows="6" [(ngModel)]="cipher.notes"
[disabled]="cipher.isDeleted || viewOnly" class="form-control"></textarea>
</div>
<h3 class="mt-4">{{'customFields' | i18n}}</h3>
<div cdkDropList (cdkDropListDropped)="drop($event)" *ngIf="cipher.hasFields">
@@ -383,14 +396,15 @@
</a>
</div>
<input id="fieldName{{i}}" type="text" name="Field.Name{{i}}" [(ngModel)]="f.name"
class="form-control" appInputVerbatim [disabled]="cipher.isDeleted">
class="form-control" appInputVerbatim [disabled]="cipher.isDeleted || viewOnly">
</div>
<div class="col-7 form-group">
<label for="fieldValue{{i}}">{{'value' | i18n}}</label>
<div class="d-flex align-items-center">
<div class="input-group" *ngIf="f.type === fieldType.Text">
<input id="fieldValue{{i}}" class="form-control" type="text" name="Field.Value{{i}}"
[(ngModel)]="f.value" appInputVerbatim [disabled]="cipher.isDeleted">
[(ngModel)]="f.value" appInputVerbatim
[disabled]="cipher.isDeleted || viewOnly">
<div class="input-group-append">
<button type="button" class="btn btn-outline-secondary"
appA11yTitle="{{'copyValue' | i18n}}"
@@ -402,8 +416,8 @@
<div class="input-group" *ngIf="f.type === fieldType.Hidden">
<input id="fieldValue{{i}}" type="{{f.showValue ? 'text' : 'password'}}"
name="Field.Value{{i}}" [(ngModel)]="f.value"
class="form-control text-monospace" appInputVerbatim
autocomplete="new-password" [disabled]="cipher.isDeleted || (!cipher.viewPassword && !f.newField)">
class="form-control text-monospace" appInputVerbatim autocomplete="new-password"
[disabled]="cipher.isDeleted || viewOnly || (!cipher.viewPassword && !f.newField)">
<div class="input-group-append">
<button type="button" class="btn btn-outline-secondary"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleFieldValue(f)"
@@ -423,24 +437,25 @@
<div class="flex-fill">
<input id="fieldValue{{i}}" name="Field.Value{{i}}" type="checkbox"
[(ngModel)]="f.value" *ngIf="f.type === fieldType.Boolean" appTrueFalseValue
trueValue="true" falseValue="false" [disabled]="cipher.isDeleted">
trueValue="true" falseValue="false" [disabled]="cipher.isDeleted || viewOnly">
</div>
<button type="button" class="btn btn-link text-danger ml-2" (click)="removeField(f)"
appA11yTitle="{{'remove' | i18n}}" *ngIf="!cipher.isDeleted">
appA11yTitle="{{'remove' | i18n}}" *ngIf="!cipher.isDeleted && !viewOnly">
<i class="fa fa-minus-circle fa-lg" aria-hidden="true"></i>
</button>
<button type="button" class="btn btn-link text-muted cursor-move"
appA11yTitle="{{'dragToSort' | i18n}}" *ngIf="!cipher.isDeleted">
appA11yTitle="{{'dragToSort' | i18n}}" *ngIf="!cipher.isDeleted && !viewOnly">
<i class="fa fa-bars fa-lg" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
</div>
<a href="#" appStopClick (click)="addField()" class="d-inline-block mb-2" *ngIf="!cipher.isDeleted">
<a href="#" appStopClick (click)="addField()" class="d-inline-block mb-2"
*ngIf="!cipher.isDeleted && !viewOnly">
<i class="fa fa-plus-circle fa-fw" aria-hidden="true"></i> {{'newCustomField' | i18n}}
</a>
<div class="row" *ngIf="!cipher.isDeleted">
<div class="row" *ngIf="!cipher.isDeleted && !viewOnly">
<div class="col-5">
<label for="addFieldType" class="sr-only">{{'type' | i18n}}</label>
<select id="addFieldType" class="form-control" name="AddFieldType" [(ngModel)]="addFieldType">
@@ -455,7 +470,7 @@
<label for="organizationId">{{'whoOwnsThisItem' | i18n}}</label>
<select id="organizationId" class="form-control" name="OrganizationId"
[(ngModel)]="cipher.organizationId" (change)="organizationChanged()"
[disabled]="cipher.isDeleted">
[disabled]="cipher.isDeleted || viewOnly">
<option *ngFor="let o of ownershipOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
@@ -469,7 +484,8 @@
<ng-container *ngIf="collections && collections.length">
<div class="form-check" *ngFor="let c of collections; let i = index">
<input class="form-check-input" type="checkbox" [(ngModel)]="c.checked"
id="collection-{{i}}" name="Collection[{{i}}].Checked" [disabled]="cipher.isDeleted">
id="collection-{{i}}" name="Collection[{{i}}].Checked"
[disabled]="cipher.isDeleted || viewOnly">
<label class="form-check-label" for="collection-{{i}}">{{c.name}}</label>
</div>
</ng-container>
@@ -493,30 +509,29 @@
<div class="ml-3" *ngIf="viewingPasswordHistory">
<div *ngFor="let ph of cipher.passwordHistory">
{{ph.lastUsedDate | date:'short'}} -
<span class="text-monospace ml-2">{{ph.password}}</span>
<span class="password-wrapper text-monospace ml-2">{{ph.password}}</span>
</div>
</div>
</div>
</ng-container>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading" *ngIf="!viewOnly">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{(cipher?.isDeleted ? 'restore' : 'save') | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{'cancel' | i18n}}
{{(viewOnly ? 'close' : 'cancel') | i18n}}
</button>
<div class="ml-auto" *ngIf="cipher">
<button *ngIf="!organization && !cipher.isDeleted" type="button" (click)="toggleFavorite()" class="btn btn-link"
appA11yTitle="{{(cipher.favorite ? 'unfavorite' : 'favorite') | i18n}}">
<div class="ml-auto" *ngIf="cipher && !viewOnly">
<button *ngIf="!organization && !cipher.isDeleted" type="button" (click)="toggleFavorite()"
class="btn btn-link" appA11yTitle="{{(cipher.favorite ? 'unfavorite' : 'favorite') | i18n}}">
<i class="fa fa-lg" [ngClass]="{'fa-star': cipher.favorite, 'fa-star-o': !cipher.favorite}"
aria-hidden="true"></i>
</button>
<button #deleteBtn type="button" (click)="delete()" class="btn btn-outline-danger"
appA11yTitle="{{(cipher.isDeleted ? 'permanentlyDelete' : 'delete') | i18n}}"
*ngIf="editMode && !cloneMode" [disabled]="deleteBtn.loading"
[appApiAction]="deletePromise">
*ngIf="editMode && !cloneMode" [disabled]="deleteBtn.loading" [appApiAction]="deletePromise">
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"
title="{{'loading' | i18n}}" aria-hidden="true"></i>

View File

@@ -12,6 +12,7 @@ import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { PolicyService } from 'jslib/abstractions/policy.service';
import { StateService } from 'jslib/abstractions/state.service';
import { TotpService } from 'jslib/abstractions/totp.service';
import { UserService } from 'jslib/abstractions/user.service';
@@ -33,6 +34,7 @@ export class AddEditComponent extends BaseAddEditComponent {
showRevisionDate = false;
hasPasswordHistory = false;
viewingPasswordHistory = false;
viewOnly = false;
protected totpInterval: number;
@@ -41,9 +43,10 @@ export class AddEditComponent extends BaseAddEditComponent {
auditService: AuditService, stateService: StateService,
userService: UserService, collectionService: CollectionService,
protected totpService: TotpService, protected passwordGenerationService: PasswordGenerationService,
protected messagingService: MessagingService, eventService: EventService) {
protected messagingService: MessagingService, eventService: EventService,
protected policyService: PolicyService) {
super(cipherService, folderService, i18nService, platformUtilsService, auditService, stateService,
userService, collectionService, messagingService, eventService);
userService, collectionService, messagingService, eventService, policyService);
}
async ngOnInit() {
@@ -155,7 +158,8 @@ export class AddEditComponent extends BaseAddEditComponent {
}
protected allowOwnershipAssignment() {
return (!this.editMode || this.cloneMode) && this.ownershipOptions != null && this.ownershipOptions.length > 1;
return (!this.editMode || this.cloneMode) && this.ownershipOptions != null
&& (this.ownershipOptions.length > 1 || !this.allowPersonal);
}
private async totpTick(intervalSeconds: number) {

View File

@@ -19,7 +19,7 @@
<i class="fa fa-spinner fa-lg fa-fw fa-spin" *ngIf="a.downloading"
aria-hidden="true"></i>
</td>
<td>
<td class="wrap">
<div class="d-flex">
<a href="#" appStopClick (click)="download(a)">{{a.fileName}}</a>
<div *ngIf="showFixOldAttachments(a)" class="ml-2">

View File

@@ -31,7 +31,7 @@ export class BulkDeleteComponent {
private apiService: ApiService) { }
async submit() {
if (!this.organization || !this.organization.isAdmin) {
if (!this.organization || !this.organization.canManageAllCollections) {
await this.deleteCiphers();
} else {
await this.deleteCiphersAdmin();

View File

@@ -47,6 +47,11 @@
<i class="fa fa-fw fa-clone" aria-hidden="true"></i>
{{'copyPassword' | i18n}}
</a>
<a class="dropdown-item" href="#" appStopClick (click)="copy(c, c.login.totp, 'verificationCodeTotp', 'TOTP')"
*ngIf="displayTotpCopyButton(c)">
<i class="fa fa-fw fa-clone" aria-hidden="true"></i>
{{'copyVerificationCode' | i18n}}
</a>
<a class="dropdown-item" href="#" appStopClick *ngIf="c.login.canLaunch"
(click)="launch(c.login.launchUri)">
<i class="fa fa-fw fa-share-square-o" aria-hidden="true"></i>

View File

@@ -14,6 +14,8 @@ import { EventService } from 'jslib/abstractions/event.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { SearchService } from 'jslib/abstractions/search.service';
import { TotpService } from 'jslib/abstractions/totp.service';
import { UserService } from 'jslib/abstractions/user.service';
import { CiphersComponent as BaseCiphersComponent } from 'jslib/angular/components/ciphers.component';
@@ -37,15 +39,20 @@ export class CiphersComponent extends BaseCiphersComponent implements OnDestroy
cipherType = CipherType;
actionPromise: Promise<any>;
userHasPremiumAccess = false;
constructor(searchService: SearchService, protected analytics: Angulartics2,
protected toasterService: ToasterService, protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService, protected cipherService: CipherService,
protected eventService: EventService) {
protected eventService: EventService, protected totpService: TotpService, protected userService: UserService) {
super(searchService);
this.pageSize = 200;
}
async ngOnInit() {
this.userHasPremiumAccess = await this.userService.canAccessPremium();
}
ngOnDestroy() {
this.selectAll(false);
}
@@ -117,9 +124,11 @@ export class CiphersComponent extends BaseCiphersComponent implements OnDestroy
this.actionPromise = null;
}
copy(cipher: CipherView, value: string, typeI18nKey: string, aType: string) {
if (value == null) {
async copy(cipher: CipherView, value: string, typeI18nKey: string, aType: string) {
if (value == null || !this.displayTotpCopyButton(cipher)) {
return;
} else if (value === cipher.login.totp) {
value = await this.totpService.getCode(value);
}
this.analytics.eventTrack.next({ action: 'Copied ' + aType.toLowerCase() + ' from listing.' });
@@ -127,7 +136,7 @@ export class CiphersComponent extends BaseCiphersComponent implements OnDestroy
this.toasterService.popAsync('info', null,
this.i18nService.t('valueCopied', this.i18nService.t(typeI18nKey)));
if (typeI18nKey === 'password') {
if (typeI18nKey === 'password' || typeI18nKey === 'verificationCodeTotp') {
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, cipher.id);
} else if (typeI18nKey === 'securityCode') {
this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, cipher.id);
@@ -161,6 +170,11 @@ export class CiphersComponent extends BaseCiphersComponent implements OnDestroy
return this.getSelected().map((c) => c.id);
}
displayTotpCopyButton(cipher: CipherView) {
return (cipher?.login?.hasTotp ?? false) &&
(cipher.organizationUseTotp || this.userHasPremiumAccess);
}
protected deleteCipher(id: string, permanent: boolean) {
return permanent ? this.cipherService.deleteWithServer(id) : this.cipherService.softDeleteWithServer(id);
}

View File

@@ -62,15 +62,17 @@
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
</a>
</h3>
<ul class="fa-ul card-ul carets">
<ul class="fa-ul card-ul">
<ng-template #recursiveFolders let-folders>
<li *ngFor="let f of folders"
[ngClass]="{active: selectedFolder && f.node.id === selectedFolderId}">
<div class="d-flex">
<i class="fa-li fa" title="{{'toggleCollapse' | i18n}}"
<i *ngIf="f.children.length" class="fa-li fa" title="{{'toggleCollapse' | i18n}}"
[ngClass]="{'fa-caret-right': isCollapsed(f.node), 'fa-caret-down': !isCollapsed(f.node)}"
(click)="collapse(f.node)"></i>
<a href="#" appStopClick (click)="selectFolder(f.node)">{{f.node.name}}</a>
<a href="#" appStopClick (click)="selectFolder(f.node)">
<i *ngIf="f.children.length === 0" class="fa-li fa fa-folder-o" aria-hidden="true"></i>{{f.node.name}}
</a>
<a href="#" class="text-muted ml-auto show-active" appStopClick
(click)="editFolder(f.node)" appA11yTitle="{{'editFolder' | i18n}}"
*ngIf="f.node.id">
@@ -89,13 +91,15 @@
</ng-container>
<ng-container *ngIf="showCollections && collections && collections.length">
<h3>{{'collections' | i18n}}</h3>
<ul class="fa-ul card-ul carets">
<ul class="fa-ul card-ul">
<ng-template #recursiveCollections let-collections>
<li *ngFor="let c of collections" [ngClass]="{active: c.node.id === selectedCollectionId}">
<i class="fa-li fa" title="{{'toggleCollapse' | i18n}}"
<i *ngIf="c.children.length" class="fa-li fa" title="{{'toggleCollapse' | i18n}}"
[ngClass]="{'fa-caret-right': isCollapsed(c.node), 'fa-caret-down': !isCollapsed(c.node)}"
(click)="collapse(c.node)"></i>
<a href="#" appStopClick (click)="selectCollection(c.node)">{{c.node.name}}</a>
<a href="#" appStopClick (click)="selectCollection(c.node)">
<i *ngIf="c.children.length === 0" class="fa-li fa fa-cube" aria-hidden="true"></i>{{c.node.name}}
</a>
<ul class="fa-ul card-ul carets" *ngIf="c.children.length && !isCollapsed(c.node)">
<ng-container
*ngTemplateOutlet="recursiveCollections; context:{ $implicit: c.children }">

View File

@@ -37,8 +37,8 @@ function getQsParam(name: string) {
function initiateBrowserSso(code: string, state: string) {
window.postMessage({ command: 'authResult', code: code, state: state }, '*');
let handOffMessage = ('; ' + document.cookie).split('; ssoHandOffMessage=').pop().split(';').shift();
document.cookie = 'ssoHandOffMessage=;SameSite=strict;max-age=0'
const handOffMessage = ('; ' + document.cookie).split('; ssoHandOffMessage=').pop().split(';').shift();
document.cookie = 'ssoHandOffMessage=;SameSite=strict;max-age=0';
document.getElementById('content').innerHTML =
`<p>${handOffMessage}</p>`;
}

View File

@@ -347,6 +347,9 @@
"editItem": {
"message": "Edit Item"
},
"viewItem": {
"message": "View Item"
},
"ex": {
"message": "ex.",
"description": "Short abbreviation for 'example'."
@@ -790,9 +793,15 @@
"warning": {
"message": "Warning"
},
"confirmVaultExport": {
"message": "Confirm Vault Export"
},
"exportWarningDesc": {
"message": "This export contains your vault data in an unencrypted format. You should not store or send the exported file over unsecure channels (such as email). Delete it immediately after you are done using it."
},
"encExportWarningDesc": {
"message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file."
},
"exportMasterPassword": {
"message": "Enter your master password to export your vault data."
},
@@ -1659,6 +1668,9 @@
"paymentInformation": {
"message": "Betaalinligting"
},
"billingInformation": {
"message": "Billing Information"
},
"creditCard": {
"message": "Kredietkaart"
},
@@ -2483,6 +2495,15 @@
}
}
},
"unlinkedSsoUser": {
"message": "Unlinked SSO for user $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "John Smith"
}
}
},
"device": {
"message": "Toestel"
},
@@ -2786,6 +2807,9 @@
"updateEncryptionKeyWarning": {
"message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed."
},
"updateEncryptionKeyExportWarning": {
"message": "Any encrypted exports that you have saved will also become invalid."
},
"subscription": {
"message": "Intekening"
},
@@ -3295,15 +3319,35 @@
"deletionDate": {
"message": "Deletion Date"
},
"deletionDateDesc": {
"message": "The Send will be permanently deleted on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"expirationDate": {
"message": "Expiration Date"
},
"expirationDateDesc": {
"message": "If set, access to this Send will expire on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"maxAccessCount": {
"message": "Maximum Access Count"
},
"maxAccessCountDesc": {
"message": "If set, users will no longer be able to access this send once the maximum access count is reached.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"currentAccessCount": {
"message": "Current Access Count"
},
"sendPasswordDesc": {
"message": "Optionally require a password for users to access this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendNotesDesc": {
"message": "Private notes about this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"disabled": {
"message": "Disabled"
},
@@ -3324,9 +3368,22 @@
"removePasswordConfirmation": {
"message": "Are you sure you want to remove the password?"
},
"disableThisSend": {
"message": "Disable this Send so that no one can access it.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"allSends": {
"message": "All Sends"
},
"maxAccessCountReached": {
"message": "Max access count reached"
},
"pendingDeletion": {
"message": "Pending deletion"
},
"expired": {
"message": "Expired"
},
"searchSends": {
"message": "Search Sends",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
@@ -3346,8 +3403,230 @@
"downloadFile": {
"message": "Download File"
},
"sendAccessUnavailable": {
"message": "The Send you are trying to access does not exist or is no longer available.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"noSendsInList": {
"message": "There are no Sends to list.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"emergencyAccess": {
"message": "Emergency Access"
},
"emergencyAccessDesc": {
"message": "Grant and manage emergency access for trusted contacts. Trusted contacts may request access to either View or Takeover your account in case of a emergency. Visit our help page for more information and details into how zero knowledge sharing works."
},
"trustedEmergencyContacts": {
"message": "Trusted emergency contacts"
},
"noTrustedContacts": {
"message": "You have not added any emergency contacts yet, invite a trusted contact to get started."
},
"addEmergencyContact": {
"message": "Add emergency contact"
},
"designatedEmergencyContacts": {
"message": "Designated as emergency contact"
},
"noGrantedAccess": {
"message": "You have not been designated as an emergency contact for anyone yet."
},
"inviteEmergencyContact": {
"message": "Invite emergency contact"
},
"editEmergencyContact": {
"message": "Edit emergency contact"
},
"inviteEmergencyContactDesc": {
"message": "Invite a new emergency contact by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account."
},
"emergencyAccessRecoveryInitiated": {
"message": "Emergency Access Initiated"
},
"emergencyAccessRecoveryApproved": {
"message": "Emergency Access Approved"
},
"viewDesc": {
"message": "Can view all items in your own vault."
},
"takeover": {
"message": "Takeover"
},
"takeoverDesc": {
"message": "Can reset your account with a new master password."
},
"waitTime": {
"message": "Wait Time"
},
"waitTimeDesc": {
"message": "Time required before automatically granting access."
},
"oneDay": {
"message": "1 day"
},
"days": {
"message": "$DAYS$ days",
"placeholders": {
"days": {
"content": "$1",
"example": "1"
}
}
},
"invitedUser": {
"message": "Invited user."
},
"acceptEmergencyAccess": {
"message": "You've been invited to become an emergency contact for the user listed above. To accept the invitation, you need to log in or create a new Bitwarden account."
},
"emergencyInviteAcceptFailed": {
"message": "Unable to accept invitation. Ask the user to send a new invitation."
},
"emergencyInviteAcceptFailedShort": {
"message": "Unable to accept invitation. $DESCRIPTION$",
"placeholders": {
"description": {
"content": "$1",
"example": "You must enable 2FA on your user account before you can join this organization."
}
}
},
"emergencyInviteAcceptedDesc": {
"message": "You can access the emergency options for this user after your identity has been confirmed. We'll send you an email when that happens."
},
"requestAccess": {
"message": "Request Access"
},
"requestAccessConfirmation": {
"message": "Are you sure you want to request emergency access? You will be provided access after $WAITTIME$ day(s) or whenever the user manually approves the request.",
"placeholders": {
"waittime": {
"content": "$1",
"example": "1"
}
}
},
"requestSent": {
"message": "Emergency access requested for $USER$. We'll notify you by email when it's possible to continue.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"approve": {
"message": "Approve"
},
"reject": {
"message": "Reject"
},
"approveAccessConfirmation": {
"message": "Are you sure you want to approve emergency access? This will allow $USER$ to $ACTION$ your account.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
},
"action": {
"content": "$2",
"example": "View"
}
}
},
"emergencyApproved": {
"message": "Emergency access approved."
},
"emergencyRejected": {
"message": "Emergency access rejected"
},
"passwordResetFor": {
"message": "Password reset for $USER$. You can now login using the new password.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"personalOwnership": {
"message": "Personal Ownership"
},
"personalOwnershipPolicyDesc": {
"message": "Require users to save vault items to an organization by removing the personal ownership option."
},
"personalOwnershipExemption": {
"message": "Organization Owners and Administrators are exempt from this policy's enforcement."
},
"personalOwnershipSubmitError": {
"message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available Collections."
},
"modifiedPolicyId": {
"message": "Modified policy $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "Master Password"
}
}
},
"planPrice": {
"message": "Plan price"
},
"estimatedTax": {
"message": "Estimated tax"
},
"custom": {
"message": "Custom"
},
"customDesc": {
"message": "Allows more granular control of user permissions for advanced configurations."
},
"permissions": {
"message": "Permissions"
},
"accessBusinessPortal": {
"message": "Access Business Portal"
},
"accessEventLogs": {
"message": "Access Event Logs"
},
"accessImportExport": {
"message": "Access Import/Export"
},
"accessReports": {
"message": "Access Reports"
},
"manageAllCollections": {
"message": "Manage All Collections"
},
"manageAssignedCollections": {
"message": "Manage Assigned Collections"
},
"manageGroups": {
"message": "Manage Groups"
},
"managePolicies": {
"message": "Manage Policies"
},
"manageSso": {
"message": "Manage SSO"
},
"manageUsers": {
"message": "Manage Users"
},
"disableRequireSsoError": {
"message": "You must manually disable the Single Sign-On Authentication policy before this policy can be disabled."
},
"personalOwnershipPolicyInEffect": {
"message": "An organization policy is affecting your ownership options."
},
"personalOwnershipCheckboxDesc": {
"message": "Disable personal ownership for organization users"
},
"textHiddenByDefault": {
"message": "When accessing the Send, hide the text by default",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}
}

View File

@@ -347,6 +347,9 @@
"editItem": {
"message": "Рэдагаванне элемента"
},
"viewItem": {
"message": "View Item"
},
"ex": {
"message": "напр.",
"description": "Short abbreviation for 'example'."
@@ -790,9 +793,15 @@
"warning": {
"message": "Увага"
},
"confirmVaultExport": {
"message": "Confirm Vault Export"
},
"exportWarningDesc": {
"message": "Экспартуемы файл утрымлівае даныя вашага сховішча ў незашыфраваным фармаце. Яго не варта захоўваць ці адпраўляць па небяспечным каналам (напрыклад, па электроннай пошце). Выдаліце яго адразу пасля выкарыстання."
},
"encExportWarningDesc": {
"message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file."
},
"exportMasterPassword": {
"message": "Увядзіце ваш асноўны пароль для экспарту даных са сховішча."
},
@@ -1659,6 +1668,9 @@
"paymentInformation": {
"message": "Payment Information"
},
"billingInformation": {
"message": "Billing Information"
},
"creditCard": {
"message": "Credit Card"
},
@@ -2483,6 +2495,15 @@
}
}
},
"unlinkedSsoUser": {
"message": "Unlinked SSO for user $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "John Smith"
}
}
},
"device": {
"message": "Device"
},
@@ -2786,6 +2807,9 @@
"updateEncryptionKeyWarning": {
"message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed."
},
"updateEncryptionKeyExportWarning": {
"message": "Any encrypted exports that you have saved will also become invalid."
},
"subscription": {
"message": "Subscription"
},
@@ -3295,15 +3319,35 @@
"deletionDate": {
"message": "Deletion Date"
},
"deletionDateDesc": {
"message": "The Send will be permanently deleted on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"expirationDate": {
"message": "Expiration Date"
},
"expirationDateDesc": {
"message": "If set, access to this Send will expire on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"maxAccessCount": {
"message": "Maximum Access Count"
},
"maxAccessCountDesc": {
"message": "If set, users will no longer be able to access this send once the maximum access count is reached.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"currentAccessCount": {
"message": "Current Access Count"
},
"sendPasswordDesc": {
"message": "Optionally require a password for users to access this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendNotesDesc": {
"message": "Private notes about this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"disabled": {
"message": "Disabled"
},
@@ -3324,9 +3368,22 @@
"removePasswordConfirmation": {
"message": "Are you sure you want to remove the password?"
},
"disableThisSend": {
"message": "Disable this Send so that no one can access it.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"allSends": {
"message": "All Sends"
},
"maxAccessCountReached": {
"message": "Max access count reached"
},
"pendingDeletion": {
"message": "Pending deletion"
},
"expired": {
"message": "Expired"
},
"searchSends": {
"message": "Search Sends",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
@@ -3346,8 +3403,230 @@
"downloadFile": {
"message": "Download File"
},
"sendAccessUnavailable": {
"message": "The Send you are trying to access does not exist or is no longer available.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"noSendsInList": {
"message": "There are no Sends to list.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"emergencyAccess": {
"message": "Emergency Access"
},
"emergencyAccessDesc": {
"message": "Grant and manage emergency access for trusted contacts. Trusted contacts may request access to either View or Takeover your account in case of a emergency. Visit our help page for more information and details into how zero knowledge sharing works."
},
"trustedEmergencyContacts": {
"message": "Trusted emergency contacts"
},
"noTrustedContacts": {
"message": "You have not added any emergency contacts yet, invite a trusted contact to get started."
},
"addEmergencyContact": {
"message": "Add emergency contact"
},
"designatedEmergencyContacts": {
"message": "Designated as emergency contact"
},
"noGrantedAccess": {
"message": "You have not been designated as an emergency contact for anyone yet."
},
"inviteEmergencyContact": {
"message": "Invite emergency contact"
},
"editEmergencyContact": {
"message": "Edit emergency contact"
},
"inviteEmergencyContactDesc": {
"message": "Invite a new emergency contact by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account."
},
"emergencyAccessRecoveryInitiated": {
"message": "Emergency Access Initiated"
},
"emergencyAccessRecoveryApproved": {
"message": "Emergency Access Approved"
},
"viewDesc": {
"message": "Can view all items in your own vault."
},
"takeover": {
"message": "Takeover"
},
"takeoverDesc": {
"message": "Can reset your account with a new master password."
},
"waitTime": {
"message": "Wait Time"
},
"waitTimeDesc": {
"message": "Time required before automatically granting access."
},
"oneDay": {
"message": "1 day"
},
"days": {
"message": "$DAYS$ days",
"placeholders": {
"days": {
"content": "$1",
"example": "1"
}
}
},
"invitedUser": {
"message": "Invited user."
},
"acceptEmergencyAccess": {
"message": "You've been invited to become an emergency contact for the user listed above. To accept the invitation, you need to log in or create a new Bitwarden account."
},
"emergencyInviteAcceptFailed": {
"message": "Unable to accept invitation. Ask the user to send a new invitation."
},
"emergencyInviteAcceptFailedShort": {
"message": "Unable to accept invitation. $DESCRIPTION$",
"placeholders": {
"description": {
"content": "$1",
"example": "You must enable 2FA on your user account before you can join this organization."
}
}
},
"emergencyInviteAcceptedDesc": {
"message": "You can access the emergency options for this user after your identity has been confirmed. We'll send you an email when that happens."
},
"requestAccess": {
"message": "Request Access"
},
"requestAccessConfirmation": {
"message": "Are you sure you want to request emergency access? You will be provided access after $WAITTIME$ day(s) or whenever the user manually approves the request.",
"placeholders": {
"waittime": {
"content": "$1",
"example": "1"
}
}
},
"requestSent": {
"message": "Emergency access requested for $USER$. We'll notify you by email when it's possible to continue.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"approve": {
"message": "Approve"
},
"reject": {
"message": "Reject"
},
"approveAccessConfirmation": {
"message": "Are you sure you want to approve emergency access? This will allow $USER$ to $ACTION$ your account.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
},
"action": {
"content": "$2",
"example": "View"
}
}
},
"emergencyApproved": {
"message": "Emergency access approved."
},
"emergencyRejected": {
"message": "Emergency access rejected"
},
"passwordResetFor": {
"message": "Password reset for $USER$. You can now login using the new password.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"personalOwnership": {
"message": "Personal Ownership"
},
"personalOwnershipPolicyDesc": {
"message": "Require users to save vault items to an organization by removing the personal ownership option."
},
"personalOwnershipExemption": {
"message": "Organization Owners and Administrators are exempt from this policy's enforcement."
},
"personalOwnershipSubmitError": {
"message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available Collections."
},
"modifiedPolicyId": {
"message": "Modified policy $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "Master Password"
}
}
},
"planPrice": {
"message": "Plan price"
},
"estimatedTax": {
"message": "Estimated tax"
},
"custom": {
"message": "Custom"
},
"customDesc": {
"message": "Allows more granular control of user permissions for advanced configurations."
},
"permissions": {
"message": "Permissions"
},
"accessBusinessPortal": {
"message": "Access Business Portal"
},
"accessEventLogs": {
"message": "Access Event Logs"
},
"accessImportExport": {
"message": "Access Import/Export"
},
"accessReports": {
"message": "Access Reports"
},
"manageAllCollections": {
"message": "Manage All Collections"
},
"manageAssignedCollections": {
"message": "Manage Assigned Collections"
},
"manageGroups": {
"message": "Manage Groups"
},
"managePolicies": {
"message": "Manage Policies"
},
"manageSso": {
"message": "Manage SSO"
},
"manageUsers": {
"message": "Manage Users"
},
"disableRequireSsoError": {
"message": "You must manually disable the Single Sign-On Authentication policy before this policy can be disabled."
},
"personalOwnershipPolicyInEffect": {
"message": "An organization policy is affecting your ownership options."
},
"personalOwnershipCheckboxDesc": {
"message": "Disable personal ownership for organization users"
},
"textHiddenByDefault": {
"message": "When accessing the Send, hide the text by default",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}
}

View File

@@ -347,6 +347,9 @@
"editItem": {
"message": "Редактиране на елемента"
},
"viewItem": {
"message": "View Item"
},
"ex": {
"message": "напр.",
"description": "Short abbreviation for 'example'."
@@ -790,9 +793,15 @@
"warning": {
"message": "ВНИМАНИЕ"
},
"confirmVaultExport": {
"message": "Confirm Vault Export"
},
"exportWarningDesc": {
"message": "Данните от трезора ви ще се изнесат в незащитен формат. Не го пращайте по незащитени канали като електронна поща. Изтрийте файла незабавно след като свършите работата си с него."
},
"encExportWarningDesc": {
"message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file."
},
"exportMasterPassword": {
"message": "Въведете главната парола, за да изнесете данните."
},
@@ -1659,6 +1668,9 @@
"paymentInformation": {
"message": "Информация за плащането"
},
"billingInformation": {
"message": "Billing Information"
},
"creditCard": {
"message": "Кредитна карта"
},
@@ -2483,6 +2495,15 @@
}
}
},
"unlinkedSsoUser": {
"message": "Unlinked SSO for user $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "John Smith"
}
}
},
"device": {
"message": "Устройство"
},
@@ -2786,6 +2807,9 @@
"updateEncryptionKeyWarning": {
"message": "След смяната на ключа за шифриране ще трябва да се отпишете и след това да се впишете в регистрацията си във всички приложения на Битуорден, които ползвате (като мобилното приложение и разширенията за браузъри). Ако не се отпишете и впишете повторно (за да получите достъп до новия ключ), рискувате да повредите записите си. Сега ще се пробва да бъдете отписани автоматично, това обаче може да се забави."
},
"updateEncryptionKeyExportWarning": {
"message": "Any encrypted exports that you have saved will also become invalid."
},
"subscription": {
"message": "Абонамент"
},
@@ -3295,15 +3319,35 @@
"deletionDate": {
"message": "Deletion Date"
},
"deletionDateDesc": {
"message": "The Send will be permanently deleted on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"expirationDate": {
"message": "Срок на валидност"
},
"expirationDateDesc": {
"message": "If set, access to this Send will expire on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"maxAccessCount": {
"message": "Maximum Access Count"
},
"maxAccessCountDesc": {
"message": "If set, users will no longer be able to access this send once the maximum access count is reached.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"currentAccessCount": {
"message": "Current Access Count"
},
"sendPasswordDesc": {
"message": "Optionally require a password for users to access this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendNotesDesc": {
"message": "Private notes about this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"disabled": {
"message": "Disabled"
},
@@ -3324,9 +3368,22 @@
"removePasswordConfirmation": {
"message": "Are you sure you want to remove the password?"
},
"disableThisSend": {
"message": "Disable this Send so that no one can access it.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"allSends": {
"message": "All Sends"
},
"maxAccessCountReached": {
"message": "Max access count reached"
},
"pendingDeletion": {
"message": "Pending deletion"
},
"expired": {
"message": "Expired"
},
"searchSends": {
"message": "Search Sends",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
@@ -3346,8 +3403,230 @@
"downloadFile": {
"message": "Изтеглете файл"
},
"sendAccessUnavailable": {
"message": "The Send you are trying to access does not exist or is no longer available.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"noSendsInList": {
"message": "There are no Sends to list.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"emergencyAccess": {
"message": "Emergency Access"
},
"emergencyAccessDesc": {
"message": "Grant and manage emergency access for trusted contacts. Trusted contacts may request access to either View or Takeover your account in case of a emergency. Visit our help page for more information and details into how zero knowledge sharing works."
},
"trustedEmergencyContacts": {
"message": "Trusted emergency contacts"
},
"noTrustedContacts": {
"message": "You have not added any emergency contacts yet, invite a trusted contact to get started."
},
"addEmergencyContact": {
"message": "Add emergency contact"
},
"designatedEmergencyContacts": {
"message": "Designated as emergency contact"
},
"noGrantedAccess": {
"message": "You have not been designated as an emergency contact for anyone yet."
},
"inviteEmergencyContact": {
"message": "Invite emergency contact"
},
"editEmergencyContact": {
"message": "Edit emergency contact"
},
"inviteEmergencyContactDesc": {
"message": "Invite a new emergency contact by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account."
},
"emergencyAccessRecoveryInitiated": {
"message": "Emergency Access Initiated"
},
"emergencyAccessRecoveryApproved": {
"message": "Emergency Access Approved"
},
"viewDesc": {
"message": "Can view all items in your own vault."
},
"takeover": {
"message": "Takeover"
},
"takeoverDesc": {
"message": "Can reset your account with a new master password."
},
"waitTime": {
"message": "Wait Time"
},
"waitTimeDesc": {
"message": "Time required before automatically granting access."
},
"oneDay": {
"message": "1 day"
},
"days": {
"message": "$DAYS$ days",
"placeholders": {
"days": {
"content": "$1",
"example": "1"
}
}
},
"invitedUser": {
"message": "Invited user."
},
"acceptEmergencyAccess": {
"message": "You've been invited to become an emergency contact for the user listed above. To accept the invitation, you need to log in or create a new Bitwarden account."
},
"emergencyInviteAcceptFailed": {
"message": "Unable to accept invitation. Ask the user to send a new invitation."
},
"emergencyInviteAcceptFailedShort": {
"message": "Unable to accept invitation. $DESCRIPTION$",
"placeholders": {
"description": {
"content": "$1",
"example": "You must enable 2FA on your user account before you can join this organization."
}
}
},
"emergencyInviteAcceptedDesc": {
"message": "You can access the emergency options for this user after your identity has been confirmed. We'll send you an email when that happens."
},
"requestAccess": {
"message": "Request Access"
},
"requestAccessConfirmation": {
"message": "Are you sure you want to request emergency access? You will be provided access after $WAITTIME$ day(s) or whenever the user manually approves the request.",
"placeholders": {
"waittime": {
"content": "$1",
"example": "1"
}
}
},
"requestSent": {
"message": "Emergency access requested for $USER$. We'll notify you by email when it's possible to continue.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"approve": {
"message": "Approve"
},
"reject": {
"message": "Reject"
},
"approveAccessConfirmation": {
"message": "Are you sure you want to approve emergency access? This will allow $USER$ to $ACTION$ your account.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
},
"action": {
"content": "$2",
"example": "View"
}
}
},
"emergencyApproved": {
"message": "Emergency access approved."
},
"emergencyRejected": {
"message": "Emergency access rejected"
},
"passwordResetFor": {
"message": "Password reset for $USER$. You can now login using the new password.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"personalOwnership": {
"message": "Personal Ownership"
},
"personalOwnershipPolicyDesc": {
"message": "Require users to save vault items to an organization by removing the personal ownership option."
},
"personalOwnershipExemption": {
"message": "Organization Owners and Administrators are exempt from this policy's enforcement."
},
"personalOwnershipSubmitError": {
"message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available Collections."
},
"modifiedPolicyId": {
"message": "Modified policy $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "Master Password"
}
}
},
"planPrice": {
"message": "Plan price"
},
"estimatedTax": {
"message": "Estimated tax"
},
"custom": {
"message": "Custom"
},
"customDesc": {
"message": "Allows more granular control of user permissions for advanced configurations."
},
"permissions": {
"message": "Permissions"
},
"accessBusinessPortal": {
"message": "Access Business Portal"
},
"accessEventLogs": {
"message": "Access Event Logs"
},
"accessImportExport": {
"message": "Access Import/Export"
},
"accessReports": {
"message": "Access Reports"
},
"manageAllCollections": {
"message": "Manage All Collections"
},
"manageAssignedCollections": {
"message": "Manage Assigned Collections"
},
"manageGroups": {
"message": "Manage Groups"
},
"managePolicies": {
"message": "Manage Policies"
},
"manageSso": {
"message": "Manage SSO"
},
"manageUsers": {
"message": "Manage Users"
},
"disableRequireSsoError": {
"message": "You must manually disable the Single Sign-On Authentication policy before this policy can be disabled."
},
"personalOwnershipPolicyInEffect": {
"message": "An organization policy is affecting your ownership options."
},
"personalOwnershipCheckboxDesc": {
"message": "Disable personal ownership for organization users"
},
"textHiddenByDefault": {
"message": "When accessing the Send, hide the text by default",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}
}

View File

@@ -347,6 +347,9 @@
"editItem": {
"message": "Edita l'element"
},
"viewItem": {
"message": "View Item"
},
"ex": {
"message": "ex.",
"description": "Short abbreviation for 'example'."
@@ -790,9 +793,15 @@
"warning": {
"message": "Advertiment"
},
"confirmVaultExport": {
"message": "Confirm Vault Export"
},
"exportWarningDesc": {
"message": "Aquesta exportació conté les dades de la vostra caixa forta en un format no xifrat. No hauríeu d'emmagatzemar o enviar el fitxer exportat a través de canals no segurs (com ara el correu electrònic). Elimineu-lo immediatament després d'haver acabat d'usar-lo."
},
"encExportWarningDesc": {
"message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file."
},
"exportMasterPassword": {
"message": "Introduïu la contrasenya mestra per exportar les dades de la caixa forta."
},
@@ -1659,6 +1668,9 @@
"paymentInformation": {
"message": "Informació de pagament"
},
"billingInformation": {
"message": "Billing Information"
},
"creditCard": {
"message": "Targeta de crèdit"
},
@@ -2483,6 +2495,15 @@
}
}
},
"unlinkedSsoUser": {
"message": "Unlinked SSO for user $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "John Smith"
}
}
},
"device": {
"message": "Dispositiu"
},
@@ -2786,6 +2807,9 @@
"updateEncryptionKeyWarning": {
"message": "Després d'actualitzar la vostra clau de xifratge, heu de tancar la sessió i tornar a entrar a totes les aplicacions de Bitwarden que esteu utilitzant actualment (com ara l'aplicació mòbil o les extensions del navegador). Si no es tanca i torna a iniciar la sessió (la qual descarrega la vostra nova clau de xifratge) pot provocar corrupció en les dades. Intentarem registrar-vos automàticament, però, es pot retardar."
},
"updateEncryptionKeyExportWarning": {
"message": "Any encrypted exports that you have saved will also become invalid."
},
"subscription": {
"message": "Subscripció"
},
@@ -3194,7 +3218,7 @@
"message": "Inici de sessió únic d'empresa"
},
"ssoHandOff": {
"message": "You may now close this tab and continue in the extension."
"message": "Ara podeu tancar aquesta pestanya i continuar a l'extensió."
},
"businessPortal": {
"message": "Portal empresarial",
@@ -3295,15 +3319,35 @@
"deletionDate": {
"message": "Data de supressió"
},
"deletionDateDesc": {
"message": "The Send will be permanently deleted on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"expirationDate": {
"message": "Data de venciment"
},
"expirationDateDesc": {
"message": "If set, access to this Send will expire on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"maxAccessCount": {
"message": "Recompte màxim d'accés"
},
"maxAccessCountDesc": {
"message": "If set, users will no longer be able to access this send once the maximum access count is reached.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"currentAccessCount": {
"message": "Recompte daccés actual"
},
"sendPasswordDesc": {
"message": "Optionally require a password for users to access this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendNotesDesc": {
"message": "Private notes about this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"disabled": {
"message": "Deshabilitat"
},
@@ -3324,9 +3368,22 @@
"removePasswordConfirmation": {
"message": "Esteu segur que voleu suprimir la contrasenya?"
},
"disableThisSend": {
"message": "Disable this Send so that no one can access it.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"allSends": {
"message": "Tots els Send"
},
"maxAccessCountReached": {
"message": "Max access count reached"
},
"pendingDeletion": {
"message": "Pending deletion"
},
"expired": {
"message": "Expired"
},
"searchSends": {
"message": "Cerca Sends",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
@@ -3346,8 +3403,230 @@
"downloadFile": {
"message": "Baixa el fitxer"
},
"sendAccessUnavailable": {
"message": "The Send you are trying to access does not exist or is no longer available.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"noSendsInList": {
"message": "No hi ha cap Send a llistar.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"emergencyAccess": {
"message": "Emergency Access"
},
"emergencyAccessDesc": {
"message": "Grant and manage emergency access for trusted contacts. Trusted contacts may request access to either View or Takeover your account in case of a emergency. Visit our help page for more information and details into how zero knowledge sharing works."
},
"trustedEmergencyContacts": {
"message": "Trusted emergency contacts"
},
"noTrustedContacts": {
"message": "You have not added any emergency contacts yet, invite a trusted contact to get started."
},
"addEmergencyContact": {
"message": "Add emergency contact"
},
"designatedEmergencyContacts": {
"message": "Designated as emergency contact"
},
"noGrantedAccess": {
"message": "You have not been designated as an emergency contact for anyone yet."
},
"inviteEmergencyContact": {
"message": "Invite emergency contact"
},
"editEmergencyContact": {
"message": "Edit emergency contact"
},
"inviteEmergencyContactDesc": {
"message": "Invite a new emergency contact by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account."
},
"emergencyAccessRecoveryInitiated": {
"message": "Emergency Access Initiated"
},
"emergencyAccessRecoveryApproved": {
"message": "Emergency Access Approved"
},
"viewDesc": {
"message": "Can view all items in your own vault."
},
"takeover": {
"message": "Takeover"
},
"takeoverDesc": {
"message": "Can reset your account with a new master password."
},
"waitTime": {
"message": "Wait Time"
},
"waitTimeDesc": {
"message": "Time required before automatically granting access."
},
"oneDay": {
"message": "1 day"
},
"days": {
"message": "$DAYS$ days",
"placeholders": {
"days": {
"content": "$1",
"example": "1"
}
}
},
"invitedUser": {
"message": "Invited user."
},
"acceptEmergencyAccess": {
"message": "You've been invited to become an emergency contact for the user listed above. To accept the invitation, you need to log in or create a new Bitwarden account."
},
"emergencyInviteAcceptFailed": {
"message": "Unable to accept invitation. Ask the user to send a new invitation."
},
"emergencyInviteAcceptFailedShort": {
"message": "Unable to accept invitation. $DESCRIPTION$",
"placeholders": {
"description": {
"content": "$1",
"example": "You must enable 2FA on your user account before you can join this organization."
}
}
},
"emergencyInviteAcceptedDesc": {
"message": "You can access the emergency options for this user after your identity has been confirmed. We'll send you an email when that happens."
},
"requestAccess": {
"message": "Request Access"
},
"requestAccessConfirmation": {
"message": "Are you sure you want to request emergency access? You will be provided access after $WAITTIME$ day(s) or whenever the user manually approves the request.",
"placeholders": {
"waittime": {
"content": "$1",
"example": "1"
}
}
},
"requestSent": {
"message": "Emergency access requested for $USER$. We'll notify you by email when it's possible to continue.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"approve": {
"message": "Approve"
},
"reject": {
"message": "Reject"
},
"approveAccessConfirmation": {
"message": "Are you sure you want to approve emergency access? This will allow $USER$ to $ACTION$ your account.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
},
"action": {
"content": "$2",
"example": "View"
}
}
},
"emergencyApproved": {
"message": "Emergency access approved."
},
"emergencyRejected": {
"message": "Emergency access rejected"
},
"passwordResetFor": {
"message": "Password reset for $USER$. You can now login using the new password.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"personalOwnership": {
"message": "Personal Ownership"
},
"personalOwnershipPolicyDesc": {
"message": "Require users to save vault items to an organization by removing the personal ownership option."
},
"personalOwnershipExemption": {
"message": "Organization Owners and Administrators are exempt from this policy's enforcement."
},
"personalOwnershipSubmitError": {
"message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available Collections."
},
"modifiedPolicyId": {
"message": "Modified policy $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "Master Password"
}
}
},
"planPrice": {
"message": "Plan price"
},
"estimatedTax": {
"message": "Estimated tax"
},
"custom": {
"message": "Custom"
},
"customDesc": {
"message": "Allows more granular control of user permissions for advanced configurations."
},
"permissions": {
"message": "Permissions"
},
"accessBusinessPortal": {
"message": "Access Business Portal"
},
"accessEventLogs": {
"message": "Access Event Logs"
},
"accessImportExport": {
"message": "Access Import/Export"
},
"accessReports": {
"message": "Access Reports"
},
"manageAllCollections": {
"message": "Manage All Collections"
},
"manageAssignedCollections": {
"message": "Manage Assigned Collections"
},
"manageGroups": {
"message": "Manage Groups"
},
"managePolicies": {
"message": "Manage Policies"
},
"manageSso": {
"message": "Manage SSO"
},
"manageUsers": {
"message": "Manage Users"
},
"disableRequireSsoError": {
"message": "You must manually disable the Single Sign-On Authentication policy before this policy can be disabled."
},
"personalOwnershipPolicyInEffect": {
"message": "An organization policy is affecting your ownership options."
},
"personalOwnershipCheckboxDesc": {
"message": "Disable personal ownership for organization users"
},
"textHiddenByDefault": {
"message": "When accessing the Send, hide the text by default",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}
}

View File

@@ -347,6 +347,9 @@
"editItem": {
"message": "Upravit položku"
},
"viewItem": {
"message": "View Item"
},
"ex": {
"message": "např.",
"description": "Short abbreviation for 'example'."
@@ -790,9 +793,15 @@
"warning": {
"message": "Varování"
},
"confirmVaultExport": {
"message": "Confirm Vault Export"
},
"exportWarningDesc": {
"message": "Tento export obsahuje data vašeho trezoru v nezašifrovaném formátu. Soubor exportu byste neměli ukládat ani odesílat přes nezabezpečené kanály (např. e-mailem). Odstraňte jej okamžitě po jeho použití."
},
"encExportWarningDesc": {
"message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file."
},
"exportMasterPassword": {
"message": "Zadejte své hlavní heslo pro export dat."
},
@@ -1659,6 +1668,9 @@
"paymentInformation": {
"message": "Informace o platbě"
},
"billingInformation": {
"message": "Billing Information"
},
"creditCard": {
"message": "Kreditní karta"
},
@@ -2483,6 +2495,15 @@
}
}
},
"unlinkedSsoUser": {
"message": "Unlinked SSO for user $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "John Smith"
}
}
},
"device": {
"message": "Zařízení"
},
@@ -2786,6 +2807,9 @@
"updateEncryptionKeyWarning": {
"message": "Po aktualizace šifrovacího klíče dojde k odhlášení a budete se muset opětovně přihlásit do všech Bitwarden aplikací, které aktuálně používáte (např. mobilní aplikace či rozšíření pro prohlížeč). Nezdaří-li se odhlášení a opětovné přihlášení (během něhož bude stažen nový šifrovací klíč), může dojít k poškození údajů. Pokusíme se vás automaticky odhlásit, nicméně, může to chvíli trvat."
},
"updateEncryptionKeyExportWarning": {
"message": "Any encrypted exports that you have saved will also become invalid."
},
"subscription": {
"message": "Odběr"
},
@@ -3295,15 +3319,35 @@
"deletionDate": {
"message": "Datum odstranění"
},
"deletionDateDesc": {
"message": "The Send will be permanently deleted on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"expirationDate": {
"message": "Datum expirace"
},
"expirationDateDesc": {
"message": "If set, access to this Send will expire on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"maxAccessCount": {
"message": "Maximum Access Count"
},
"maxAccessCountDesc": {
"message": "If set, users will no longer be able to access this send once the maximum access count is reached.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"currentAccessCount": {
"message": "Current Access Count"
},
"sendPasswordDesc": {
"message": "Optionally require a password for users to access this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendNotesDesc": {
"message": "Private notes about this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"disabled": {
"message": "Disabled"
},
@@ -3324,9 +3368,22 @@
"removePasswordConfirmation": {
"message": "Are you sure you want to remove the password?"
},
"disableThisSend": {
"message": "Disable this Send so that no one can access it.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"allSends": {
"message": "All Sends"
},
"maxAccessCountReached": {
"message": "Max access count reached"
},
"pendingDeletion": {
"message": "Pending deletion"
},
"expired": {
"message": "Expired"
},
"searchSends": {
"message": "Search Sends",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
@@ -3346,8 +3403,230 @@
"downloadFile": {
"message": "Stáhnout soubor"
},
"sendAccessUnavailable": {
"message": "The Send you are trying to access does not exist or is no longer available.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"noSendsInList": {
"message": "There are no Sends to list.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"emergencyAccess": {
"message": "Emergency Access"
},
"emergencyAccessDesc": {
"message": "Grant and manage emergency access for trusted contacts. Trusted contacts may request access to either View or Takeover your account in case of a emergency. Visit our help page for more information and details into how zero knowledge sharing works."
},
"trustedEmergencyContacts": {
"message": "Trusted emergency contacts"
},
"noTrustedContacts": {
"message": "You have not added any emergency contacts yet, invite a trusted contact to get started."
},
"addEmergencyContact": {
"message": "Add emergency contact"
},
"designatedEmergencyContacts": {
"message": "Designated as emergency contact"
},
"noGrantedAccess": {
"message": "You have not been designated as an emergency contact for anyone yet."
},
"inviteEmergencyContact": {
"message": "Invite emergency contact"
},
"editEmergencyContact": {
"message": "Edit emergency contact"
},
"inviteEmergencyContactDesc": {
"message": "Invite a new emergency contact by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account."
},
"emergencyAccessRecoveryInitiated": {
"message": "Emergency Access Initiated"
},
"emergencyAccessRecoveryApproved": {
"message": "Emergency Access Approved"
},
"viewDesc": {
"message": "Can view all items in your own vault."
},
"takeover": {
"message": "Takeover"
},
"takeoverDesc": {
"message": "Can reset your account with a new master password."
},
"waitTime": {
"message": "Wait Time"
},
"waitTimeDesc": {
"message": "Time required before automatically granting access."
},
"oneDay": {
"message": "1 day"
},
"days": {
"message": "$DAYS$ days",
"placeholders": {
"days": {
"content": "$1",
"example": "1"
}
}
},
"invitedUser": {
"message": "Invited user."
},
"acceptEmergencyAccess": {
"message": "You've been invited to become an emergency contact for the user listed above. To accept the invitation, you need to log in or create a new Bitwarden account."
},
"emergencyInviteAcceptFailed": {
"message": "Unable to accept invitation. Ask the user to send a new invitation."
},
"emergencyInviteAcceptFailedShort": {
"message": "Unable to accept invitation. $DESCRIPTION$",
"placeholders": {
"description": {
"content": "$1",
"example": "You must enable 2FA on your user account before you can join this organization."
}
}
},
"emergencyInviteAcceptedDesc": {
"message": "You can access the emergency options for this user after your identity has been confirmed. We'll send you an email when that happens."
},
"requestAccess": {
"message": "Request Access"
},
"requestAccessConfirmation": {
"message": "Are you sure you want to request emergency access? You will be provided access after $WAITTIME$ day(s) or whenever the user manually approves the request.",
"placeholders": {
"waittime": {
"content": "$1",
"example": "1"
}
}
},
"requestSent": {
"message": "Emergency access requested for $USER$. We'll notify you by email when it's possible to continue.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"approve": {
"message": "Approve"
},
"reject": {
"message": "Reject"
},
"approveAccessConfirmation": {
"message": "Are you sure you want to approve emergency access? This will allow $USER$ to $ACTION$ your account.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
},
"action": {
"content": "$2",
"example": "View"
}
}
},
"emergencyApproved": {
"message": "Emergency access approved."
},
"emergencyRejected": {
"message": "Emergency access rejected"
},
"passwordResetFor": {
"message": "Password reset for $USER$. You can now login using the new password.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"personalOwnership": {
"message": "Personal Ownership"
},
"personalOwnershipPolicyDesc": {
"message": "Require users to save vault items to an organization by removing the personal ownership option."
},
"personalOwnershipExemption": {
"message": "Organization Owners and Administrators are exempt from this policy's enforcement."
},
"personalOwnershipSubmitError": {
"message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available Collections."
},
"modifiedPolicyId": {
"message": "Modified policy $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "Master Password"
}
}
},
"planPrice": {
"message": "Plan price"
},
"estimatedTax": {
"message": "Estimated tax"
},
"custom": {
"message": "Custom"
},
"customDesc": {
"message": "Allows more granular control of user permissions for advanced configurations."
},
"permissions": {
"message": "Permissions"
},
"accessBusinessPortal": {
"message": "Access Business Portal"
},
"accessEventLogs": {
"message": "Access Event Logs"
},
"accessImportExport": {
"message": "Access Import/Export"
},
"accessReports": {
"message": "Access Reports"
},
"manageAllCollections": {
"message": "Manage All Collections"
},
"manageAssignedCollections": {
"message": "Manage Assigned Collections"
},
"manageGroups": {
"message": "Manage Groups"
},
"managePolicies": {
"message": "Manage Policies"
},
"manageSso": {
"message": "Manage SSO"
},
"manageUsers": {
"message": "Manage Users"
},
"disableRequireSsoError": {
"message": "You must manually disable the Single Sign-On Authentication policy before this policy can be disabled."
},
"personalOwnershipPolicyInEffect": {
"message": "An organization policy is affecting your ownership options."
},
"personalOwnershipCheckboxDesc": {
"message": "Disable personal ownership for organization users"
},
"textHiddenByDefault": {
"message": "When accessing the Send, hide the text by default",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}
}

View File

@@ -347,6 +347,9 @@
"editItem": {
"message": "Redigér element"
},
"viewItem": {
"message": "View Item"
},
"ex": {
"message": "eks.",
"description": "Short abbreviation for 'example'."
@@ -461,7 +464,7 @@
"message": "Slet vedhæftning"
},
"deleteItemConfirmation": {
"message": "Er du sikker på, at du vil slette dette element?"
"message": "Er du sikker på, at du sende til papirkurven?"
},
"deletedItem": {
"message": "Element sendt til papirkurven"
@@ -790,9 +793,15 @@
"warning": {
"message": "Advarsel"
},
"confirmVaultExport": {
"message": "Confirm Vault Export"
},
"exportWarningDesc": {
"message": "Denne eksport indeholder dine boksdata i ukrypteret form. Du bør ikke gemme eller sende den eksporterede fil over usikre kanaler (f.eks. e-mail). Slet den straks efter at du er færdig med at bruge den."
},
"encExportWarningDesc": {
"message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file."
},
"exportMasterPassword": {
"message": "Indtast din hovedadgangskode for at eksportere dine data fra boksen."
},
@@ -1659,6 +1668,9 @@
"paymentInformation": {
"message": "Betalingsoplysninger"
},
"billingInformation": {
"message": "Billing Information"
},
"creditCard": {
"message": "Betalingskort"
},
@@ -2483,6 +2495,15 @@
}
}
},
"unlinkedSsoUser": {
"message": "Unlinked SSO for user $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "John Smith"
}
}
},
"device": {
"message": "Enhed"
},
@@ -2786,6 +2807,9 @@
"updateEncryptionKeyWarning": {
"message": "Efter opdatering af din krypteringsnøgle skal du logge ud og ind igen i alle Bitwarden-programmer, du bruger i øjeblikket (f.eks. mobilapp eller browserudvidelser). Hvis du ikke logger ud og ind (som downloader din nye krypteringsnøgle), kan det resultere i data korruption. Vi vil forsøge at logge dig ud automatisk, men det kan blive forsinket."
},
"updateEncryptionKeyExportWarning": {
"message": "Any encrypted exports that you have saved will also become invalid."
},
"subscription": {
"message": "Abonnement"
},
@@ -3194,7 +3218,7 @@
"message": "Virksomheds Single Sign On"
},
"ssoHandOff": {
"message": "You may now close this tab and continue in the extension."
"message": "Du kan nu lukke denne fane og fortsætte i udvidelsen."
},
"businessPortal": {
"message": "Virksomhedssportal",
@@ -3295,15 +3319,35 @@
"deletionDate": {
"message": "Sletningsdato"
},
"deletionDateDesc": {
"message": "The Send will be permanently deleted on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"expirationDate": {
"message": "Udløbsdato"
},
"expirationDateDesc": {
"message": "If set, access to this Send will expire on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"maxAccessCount": {
"message": "Maksimal antal tilgange"
},
"maxAccessCountDesc": {
"message": "If set, users will no longer be able to access this send once the maximum access count is reached.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"currentAccessCount": {
"message": "Aktuelt antal tilgange"
},
"sendPasswordDesc": {
"message": "Optionally require a password for users to access this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendNotesDesc": {
"message": "Private notes about this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"disabled": {
"message": "Deaktiveret"
},
@@ -3324,9 +3368,22 @@
"removePasswordConfirmation": {
"message": "Er du sikker på, at du vil fjerne adgangskoden?"
},
"disableThisSend": {
"message": "Disable this Send so that no one can access it.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"allSends": {
"message": "Alle Send"
},
"maxAccessCountReached": {
"message": "Max access count reached"
},
"pendingDeletion": {
"message": "Pending deletion"
},
"expired": {
"message": "Expired"
},
"searchSends": {
"message": "Søg Send",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
@@ -3346,8 +3403,230 @@
"downloadFile": {
"message": "Download fil"
},
"sendAccessUnavailable": {
"message": "The Send you are trying to access does not exist or is no longer available.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"noSendsInList": {
"message": "Der er ingen Send at vise.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"emergencyAccess": {
"message": "Emergency Access"
},
"emergencyAccessDesc": {
"message": "Grant and manage emergency access for trusted contacts. Trusted contacts may request access to either View or Takeover your account in case of a emergency. Visit our help page for more information and details into how zero knowledge sharing works."
},
"trustedEmergencyContacts": {
"message": "Trusted emergency contacts"
},
"noTrustedContacts": {
"message": "You have not added any emergency contacts yet, invite a trusted contact to get started."
},
"addEmergencyContact": {
"message": "Add emergency contact"
},
"designatedEmergencyContacts": {
"message": "Designated as emergency contact"
},
"noGrantedAccess": {
"message": "You have not been designated as an emergency contact for anyone yet."
},
"inviteEmergencyContact": {
"message": "Invite emergency contact"
},
"editEmergencyContact": {
"message": "Edit emergency contact"
},
"inviteEmergencyContactDesc": {
"message": "Invite a new emergency contact by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account."
},
"emergencyAccessRecoveryInitiated": {
"message": "Emergency Access Initiated"
},
"emergencyAccessRecoveryApproved": {
"message": "Emergency Access Approved"
},
"viewDesc": {
"message": "Can view all items in your own vault."
},
"takeover": {
"message": "Takeover"
},
"takeoverDesc": {
"message": "Can reset your account with a new master password."
},
"waitTime": {
"message": "Wait Time"
},
"waitTimeDesc": {
"message": "Time required before automatically granting access."
},
"oneDay": {
"message": "1 day"
},
"days": {
"message": "$DAYS$ days",
"placeholders": {
"days": {
"content": "$1",
"example": "1"
}
}
},
"invitedUser": {
"message": "Invited user."
},
"acceptEmergencyAccess": {
"message": "You've been invited to become an emergency contact for the user listed above. To accept the invitation, you need to log in or create a new Bitwarden account."
},
"emergencyInviteAcceptFailed": {
"message": "Unable to accept invitation. Ask the user to send a new invitation."
},
"emergencyInviteAcceptFailedShort": {
"message": "Unable to accept invitation. $DESCRIPTION$",
"placeholders": {
"description": {
"content": "$1",
"example": "You must enable 2FA on your user account before you can join this organization."
}
}
},
"emergencyInviteAcceptedDesc": {
"message": "You can access the emergency options for this user after your identity has been confirmed. We'll send you an email when that happens."
},
"requestAccess": {
"message": "Request Access"
},
"requestAccessConfirmation": {
"message": "Are you sure you want to request emergency access? You will be provided access after $WAITTIME$ day(s) or whenever the user manually approves the request.",
"placeholders": {
"waittime": {
"content": "$1",
"example": "1"
}
}
},
"requestSent": {
"message": "Emergency access requested for $USER$. We'll notify you by email when it's possible to continue.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"approve": {
"message": "Approve"
},
"reject": {
"message": "Reject"
},
"approveAccessConfirmation": {
"message": "Are you sure you want to approve emergency access? This will allow $USER$ to $ACTION$ your account.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
},
"action": {
"content": "$2",
"example": "View"
}
}
},
"emergencyApproved": {
"message": "Emergency access approved."
},
"emergencyRejected": {
"message": "Emergency access rejected"
},
"passwordResetFor": {
"message": "Password reset for $USER$. You can now login using the new password.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"personalOwnership": {
"message": "Personal Ownership"
},
"personalOwnershipPolicyDesc": {
"message": "Require users to save vault items to an organization by removing the personal ownership option."
},
"personalOwnershipExemption": {
"message": "Organization Owners and Administrators are exempt from this policy's enforcement."
},
"personalOwnershipSubmitError": {
"message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available Collections."
},
"modifiedPolicyId": {
"message": "Modified policy $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "Master Password"
}
}
},
"planPrice": {
"message": "Plan price"
},
"estimatedTax": {
"message": "Estimated tax"
},
"custom": {
"message": "Custom"
},
"customDesc": {
"message": "Allows more granular control of user permissions for advanced configurations."
},
"permissions": {
"message": "Permissions"
},
"accessBusinessPortal": {
"message": "Access Business Portal"
},
"accessEventLogs": {
"message": "Access Event Logs"
},
"accessImportExport": {
"message": "Access Import/Export"
},
"accessReports": {
"message": "Access Reports"
},
"manageAllCollections": {
"message": "Manage All Collections"
},
"manageAssignedCollections": {
"message": "Manage Assigned Collections"
},
"manageGroups": {
"message": "Manage Groups"
},
"managePolicies": {
"message": "Manage Policies"
},
"manageSso": {
"message": "Manage SSO"
},
"manageUsers": {
"message": "Manage Users"
},
"disableRequireSsoError": {
"message": "You must manually disable the Single Sign-On Authentication policy before this policy can be disabled."
},
"personalOwnershipPolicyInEffect": {
"message": "An organization policy is affecting your ownership options."
},
"personalOwnershipCheckboxDesc": {
"message": "Disable personal ownership for organization users"
},
"textHiddenByDefault": {
"message": "When accessing the Send, hide the text by default",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}
}

View File

@@ -347,6 +347,9 @@
"editItem": {
"message": "Eintrag bearbeiten"
},
"viewItem": {
"message": "View Item"
},
"ex": {
"message": "Bsp.",
"description": "Short abbreviation for 'example'."
@@ -790,9 +793,15 @@
"warning": {
"message": "Warnung"
},
"confirmVaultExport": {
"message": "Confirm Vault Export"
},
"exportWarningDesc": {
"message": "Dieser Export enthält Ihre Tresor-Daten in einem unverschlüsseltem Format. Sie sollten die Datei daher nicht über unsichere Kanäle (z.B. E-Mail) versenden oder speichern. Löschen Sie die Datei sofort nach ihrer Verwendung."
},
"encExportWarningDesc": {
"message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file."
},
"exportMasterPassword": {
"message": "Geben Sie das Master-Passwort ein, um Ihre Tresordaten zu exportieren."
},
@@ -1659,6 +1668,9 @@
"paymentInformation": {
"message": "Zahlungsinformationen"
},
"billingInformation": {
"message": "Billing Information"
},
"creditCard": {
"message": "Kreditkarte"
},
@@ -2483,6 +2495,15 @@
}
}
},
"unlinkedSsoUser": {
"message": "Unlinked SSO for user $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "John Smith"
}
}
},
"device": {
"message": "Gerät"
},
@@ -2786,6 +2807,9 @@
"updateEncryptionKeyWarning": {
"message": "Nach der Aktualisierung Ihres Verschlüsselungscodes, müssen Sie sich bei allen Bitwarden-Anwendungen, welche Sie momentan benutzen (wie z. B. Smartphone-App, Browser-Erweiterungen), erneut anmelden. Ein Versäumnis der Ab- und Anmeldung (welche Ihren neuen Verschlüsselungscode bezieht) könnte zu einer Beschädigung der Daten führen. Wir werden versuchen Sie automatisch auszuloggen, was jedoch verzögert geschehen kann."
},
"updateEncryptionKeyExportWarning": {
"message": "Any encrypted exports that you have saved will also become invalid."
},
"subscription": {
"message": "Abo"
},
@@ -3194,7 +3218,7 @@
"message": "Enterprise Single Sign-On"
},
"ssoHandOff": {
"message": "You may now close this tab and continue in the extension."
"message": "Sie können diesen Tab nun schließen und in der Erweiterung fortfahren."
},
"businessPortal": {
"message": "Unternehmensportal",
@@ -3295,15 +3319,35 @@
"deletionDate": {
"message": "Löschdatum"
},
"deletionDateDesc": {
"message": "The Send will be permanently deleted on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"expirationDate": {
"message": "Ablaufdatum"
},
"expirationDateDesc": {
"message": "If set, access to this Send will expire on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"maxAccessCount": {
"message": "Maximale Zugriffsanzahl"
},
"maxAccessCountDesc": {
"message": "If set, users will no longer be able to access this send once the maximum access count is reached.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"currentAccessCount": {
"message": "Aktuelle Zugriffsanzahl"
},
"sendPasswordDesc": {
"message": "Optionally require a password for users to access this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendNotesDesc": {
"message": "Private notes about this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"disabled": {
"message": "Deaktiviert"
},
@@ -3324,9 +3368,22 @@
"removePasswordConfirmation": {
"message": "Sind Sie sicher, dass Sie das Passwort entfernen möchten?"
},
"disableThisSend": {
"message": "Disable this Send so that no one can access it.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"allSends": {
"message": "Alle Sends"
},
"maxAccessCountReached": {
"message": "Max access count reached"
},
"pendingDeletion": {
"message": "Pending deletion"
},
"expired": {
"message": "Expired"
},
"searchSends": {
"message": "Sends suchen",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
@@ -3346,8 +3403,230 @@
"downloadFile": {
"message": "Datei herunterladen"
},
"sendAccessUnavailable": {
"message": "The Send you are trying to access does not exist or is no longer available.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"noSendsInList": {
"message": "Es gibt keine Sends aufzulisten.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"emergencyAccess": {
"message": "Emergency Access"
},
"emergencyAccessDesc": {
"message": "Grant and manage emergency access for trusted contacts. Trusted contacts may request access to either View or Takeover your account in case of a emergency. Visit our help page for more information and details into how zero knowledge sharing works."
},
"trustedEmergencyContacts": {
"message": "Trusted emergency contacts"
},
"noTrustedContacts": {
"message": "You have not added any emergency contacts yet, invite a trusted contact to get started."
},
"addEmergencyContact": {
"message": "Add emergency contact"
},
"designatedEmergencyContacts": {
"message": "Designated as emergency contact"
},
"noGrantedAccess": {
"message": "You have not been designated as an emergency contact for anyone yet."
},
"inviteEmergencyContact": {
"message": "Invite emergency contact"
},
"editEmergencyContact": {
"message": "Edit emergency contact"
},
"inviteEmergencyContactDesc": {
"message": "Invite a new emergency contact by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account."
},
"emergencyAccessRecoveryInitiated": {
"message": "Emergency Access Initiated"
},
"emergencyAccessRecoveryApproved": {
"message": "Emergency Access Approved"
},
"viewDesc": {
"message": "Can view all items in your own vault."
},
"takeover": {
"message": "Takeover"
},
"takeoverDesc": {
"message": "Can reset your account with a new master password."
},
"waitTime": {
"message": "Wait Time"
},
"waitTimeDesc": {
"message": "Time required before automatically granting access."
},
"oneDay": {
"message": "1 day"
},
"days": {
"message": "$DAYS$ days",
"placeholders": {
"days": {
"content": "$1",
"example": "1"
}
}
},
"invitedUser": {
"message": "Invited user."
},
"acceptEmergencyAccess": {
"message": "You've been invited to become an emergency contact for the user listed above. To accept the invitation, you need to log in or create a new Bitwarden account."
},
"emergencyInviteAcceptFailed": {
"message": "Unable to accept invitation. Ask the user to send a new invitation."
},
"emergencyInviteAcceptFailedShort": {
"message": "Unable to accept invitation. $DESCRIPTION$",
"placeholders": {
"description": {
"content": "$1",
"example": "You must enable 2FA on your user account before you can join this organization."
}
}
},
"emergencyInviteAcceptedDesc": {
"message": "You can access the emergency options for this user after your identity has been confirmed. We'll send you an email when that happens."
},
"requestAccess": {
"message": "Request Access"
},
"requestAccessConfirmation": {
"message": "Are you sure you want to request emergency access? You will be provided access after $WAITTIME$ day(s) or whenever the user manually approves the request.",
"placeholders": {
"waittime": {
"content": "$1",
"example": "1"
}
}
},
"requestSent": {
"message": "Emergency access requested for $USER$. We'll notify you by email when it's possible to continue.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"approve": {
"message": "Approve"
},
"reject": {
"message": "Reject"
},
"approveAccessConfirmation": {
"message": "Are you sure you want to approve emergency access? This will allow $USER$ to $ACTION$ your account.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
},
"action": {
"content": "$2",
"example": "View"
}
}
},
"emergencyApproved": {
"message": "Emergency access approved."
},
"emergencyRejected": {
"message": "Emergency access rejected"
},
"passwordResetFor": {
"message": "Password reset for $USER$. You can now login using the new password.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"personalOwnership": {
"message": "Personal Ownership"
},
"personalOwnershipPolicyDesc": {
"message": "Require users to save vault items to an organization by removing the personal ownership option."
},
"personalOwnershipExemption": {
"message": "Organization Owners and Administrators are exempt from this policy's enforcement."
},
"personalOwnershipSubmitError": {
"message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available Collections."
},
"modifiedPolicyId": {
"message": "Modified policy $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "Master Password"
}
}
},
"planPrice": {
"message": "Plan price"
},
"estimatedTax": {
"message": "Estimated tax"
},
"custom": {
"message": "Custom"
},
"customDesc": {
"message": "Allows more granular control of user permissions for advanced configurations."
},
"permissions": {
"message": "Permissions"
},
"accessBusinessPortal": {
"message": "Access Business Portal"
},
"accessEventLogs": {
"message": "Access Event Logs"
},
"accessImportExport": {
"message": "Access Import/Export"
},
"accessReports": {
"message": "Access Reports"
},
"manageAllCollections": {
"message": "Manage All Collections"
},
"manageAssignedCollections": {
"message": "Manage Assigned Collections"
},
"manageGroups": {
"message": "Manage Groups"
},
"managePolicies": {
"message": "Manage Policies"
},
"manageSso": {
"message": "Manage SSO"
},
"manageUsers": {
"message": "Manage Users"
},
"disableRequireSsoError": {
"message": "You must manually disable the Single Sign-On Authentication policy before this policy can be disabled."
},
"personalOwnershipPolicyInEffect": {
"message": "An organization policy is affecting your ownership options."
},
"personalOwnershipCheckboxDesc": {
"message": "Disable personal ownership for organization users"
},
"textHiddenByDefault": {
"message": "When accessing the Send, hide the text by default",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}
}

View File

@@ -347,6 +347,9 @@
"editItem": {
"message": "Επεξεργασία Στοιχείου"
},
"viewItem": {
"message": "View Item"
},
"ex": {
"message": "πχ.",
"description": "Short abbreviation for 'example'."
@@ -461,7 +464,7 @@
"message": "Διαγραφή συνημμένου"
},
"deleteItemConfirmation": {
"message": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το στοιχείο;"
"message": "Θέλετε πραγματικά να στείλετε στον κάδο απορριμμάτων;"
},
"deletedItem": {
"message": "Διαγραμμένο στοιχείο"
@@ -790,9 +793,15 @@
"warning": {
"message": "Προειδοποίηση"
},
"confirmVaultExport": {
"message": "Confirm Vault Export"
},
"exportWarningDesc": {
"message": "Αυτή η εξαγωγή περιέχει τα δεδομένα σε μη κρυπτογραφημένη μορφή. Δεν πρέπει να αποθηκεύετε ή να στείλετε το εξαγόμενο αρχείο μέσω μη ασφαλών τρόπων (όπως μέσω email). Διαγράψτε το αμέσως μόλις τελειώσετε με τη χρήση του."
},
"encExportWarningDesc": {
"message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file."
},
"exportMasterPassword": {
"message": "Πληκτρολογήστε τον κύριο κωδικό για εξαγωγή των δεδομένων vault."
},
@@ -1659,6 +1668,9 @@
"paymentInformation": {
"message": "Πληροφορίες Πληρωμής"
},
"billingInformation": {
"message": "Billing Information"
},
"creditCard": {
"message": "Πιστωτική Κάρτα"
},
@@ -2483,6 +2495,15 @@
}
}
},
"unlinkedSsoUser": {
"message": "Unlinked SSO for user $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "John Smith"
}
}
},
"device": {
"message": "Συσκευή"
},
@@ -2786,6 +2807,9 @@
"updateEncryptionKeyWarning": {
"message": "Μετά την ενημέρωση του κλειδιού κρυπτογράφησης, πρέπει να αποσυνδεθείτε και να επιστρέψετε σε όλες τις εφαρμογές Bitwarden που χρησιμοποιείτε αυτήν τη στιγμή (όπως η εφαρμογή για κινητά ή οι επεκτάσεις του προγράμματος περιήγησης). Η αποτυχία αποσύνδεσης και επαναφοράς (στην οποία γίνεται λήψη του νέου κλειδιού κρυπτογράφησης) ενδέχεται να προκαλέσει καταστροφή δεδομένων. Θα προσπαθήσουμε να αποσυνδεθείτε αυτόματα, ωστόσο αυτό μπορεί να καθυστερήσει."
},
"updateEncryptionKeyExportWarning": {
"message": "Any encrypted exports that you have saved will also become invalid."
},
"subscription": {
"message": "Συνδρομή"
},
@@ -3194,7 +3218,7 @@
"message": "Ενιαία είσοδος για επιχειρήσεις"
},
"ssoHandOff": {
"message": "You may now close this tab and continue in the extension."
"message": "Μπορείτε να κλείσετε αυτήν την καρτέλα τώρα και να συνεχίσετε στην επέκταση."
},
"businessPortal": {
"message": "Επιχειρηματική πύλη",
@@ -3295,15 +3319,35 @@
"deletionDate": {
"message": "Ημερομηνία διαγραφής"
},
"deletionDateDesc": {
"message": "The Send will be permanently deleted on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"expirationDate": {
"message": "Ημερομηνία Λήξης"
},
"expirationDateDesc": {
"message": "If set, access to this Send will expire on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"maxAccessCount": {
"message": "Μέγιστος Αριθμός Πρόσβασης"
},
"maxAccessCountDesc": {
"message": "If set, users will no longer be able to access this send once the maximum access count is reached.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"currentAccessCount": {
"message": "Τρέχων Αριθμός Πρόσβασης"
},
"sendPasswordDesc": {
"message": "Optionally require a password for users to access this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendNotesDesc": {
"message": "Private notes about this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"disabled": {
"message": "Απενεργοποιημένο"
},
@@ -3324,9 +3368,22 @@
"removePasswordConfirmation": {
"message": "Είστε βέβαιοι ότι θέλετε να καταργήσετε τον κωδικό πρόσβασης;"
},
"disableThisSend": {
"message": "Disable this Send so that no one can access it.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"allSends": {
"message": "Όλα τα Sends"
},
"maxAccessCountReached": {
"message": "Max access count reached"
},
"pendingDeletion": {
"message": "Pending deletion"
},
"expired": {
"message": "Expired"
},
"searchSends": {
"message": "Αναζήτηση Sends",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
@@ -3346,8 +3403,230 @@
"downloadFile": {
"message": "Λήψη Αρχείου"
},
"sendAccessUnavailable": {
"message": "The Send you are trying to access does not exist or is no longer available.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"noSendsInList": {
"message": "Δεν υπάρχουν Sends στη λίστα.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"emergencyAccess": {
"message": "Emergency Access"
},
"emergencyAccessDesc": {
"message": "Grant and manage emergency access for trusted contacts. Trusted contacts may request access to either View or Takeover your account in case of a emergency. Visit our help page for more information and details into how zero knowledge sharing works."
},
"trustedEmergencyContacts": {
"message": "Trusted emergency contacts"
},
"noTrustedContacts": {
"message": "You have not added any emergency contacts yet, invite a trusted contact to get started."
},
"addEmergencyContact": {
"message": "Add emergency contact"
},
"designatedEmergencyContacts": {
"message": "Designated as emergency contact"
},
"noGrantedAccess": {
"message": "You have not been designated as an emergency contact for anyone yet."
},
"inviteEmergencyContact": {
"message": "Invite emergency contact"
},
"editEmergencyContact": {
"message": "Edit emergency contact"
},
"inviteEmergencyContactDesc": {
"message": "Invite a new emergency contact by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account."
},
"emergencyAccessRecoveryInitiated": {
"message": "Emergency Access Initiated"
},
"emergencyAccessRecoveryApproved": {
"message": "Emergency Access Approved"
},
"viewDesc": {
"message": "Can view all items in your own vault."
},
"takeover": {
"message": "Takeover"
},
"takeoverDesc": {
"message": "Can reset your account with a new master password."
},
"waitTime": {
"message": "Wait Time"
},
"waitTimeDesc": {
"message": "Time required before automatically granting access."
},
"oneDay": {
"message": "1 day"
},
"days": {
"message": "$DAYS$ days",
"placeholders": {
"days": {
"content": "$1",
"example": "1"
}
}
},
"invitedUser": {
"message": "Invited user."
},
"acceptEmergencyAccess": {
"message": "You've been invited to become an emergency contact for the user listed above. To accept the invitation, you need to log in or create a new Bitwarden account."
},
"emergencyInviteAcceptFailed": {
"message": "Unable to accept invitation. Ask the user to send a new invitation."
},
"emergencyInviteAcceptFailedShort": {
"message": "Unable to accept invitation. $DESCRIPTION$",
"placeholders": {
"description": {
"content": "$1",
"example": "You must enable 2FA on your user account before you can join this organization."
}
}
},
"emergencyInviteAcceptedDesc": {
"message": "You can access the emergency options for this user after your identity has been confirmed. We'll send you an email when that happens."
},
"requestAccess": {
"message": "Request Access"
},
"requestAccessConfirmation": {
"message": "Are you sure you want to request emergency access? You will be provided access after $WAITTIME$ day(s) or whenever the user manually approves the request.",
"placeholders": {
"waittime": {
"content": "$1",
"example": "1"
}
}
},
"requestSent": {
"message": "Emergency access requested for $USER$. We'll notify you by email when it's possible to continue.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"approve": {
"message": "Approve"
},
"reject": {
"message": "Reject"
},
"approveAccessConfirmation": {
"message": "Are you sure you want to approve emergency access? This will allow $USER$ to $ACTION$ your account.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
},
"action": {
"content": "$2",
"example": "View"
}
}
},
"emergencyApproved": {
"message": "Emergency access approved."
},
"emergencyRejected": {
"message": "Emergency access rejected"
},
"passwordResetFor": {
"message": "Password reset for $USER$. You can now login using the new password.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"personalOwnership": {
"message": "Personal Ownership"
},
"personalOwnershipPolicyDesc": {
"message": "Require users to save vault items to an organization by removing the personal ownership option."
},
"personalOwnershipExemption": {
"message": "Organization Owners and Administrators are exempt from this policy's enforcement."
},
"personalOwnershipSubmitError": {
"message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available Collections."
},
"modifiedPolicyId": {
"message": "Modified policy $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "Master Password"
}
}
},
"planPrice": {
"message": "Plan price"
},
"estimatedTax": {
"message": "Estimated tax"
},
"custom": {
"message": "Custom"
},
"customDesc": {
"message": "Allows more granular control of user permissions for advanced configurations."
},
"permissions": {
"message": "Permissions"
},
"accessBusinessPortal": {
"message": "Access Business Portal"
},
"accessEventLogs": {
"message": "Access Event Logs"
},
"accessImportExport": {
"message": "Access Import/Export"
},
"accessReports": {
"message": "Access Reports"
},
"manageAllCollections": {
"message": "Manage All Collections"
},
"manageAssignedCollections": {
"message": "Manage Assigned Collections"
},
"manageGroups": {
"message": "Manage Groups"
},
"managePolicies": {
"message": "Manage Policies"
},
"manageSso": {
"message": "Manage SSO"
},
"manageUsers": {
"message": "Manage Users"
},
"disableRequireSsoError": {
"message": "You must manually disable the Single Sign-On Authentication policy before this policy can be disabled."
},
"personalOwnershipPolicyInEffect": {
"message": "An organization policy is affecting your ownership options."
},
"personalOwnershipCheckboxDesc": {
"message": "Disable personal ownership for organization users"
},
"textHiddenByDefault": {
"message": "When accessing the Send, hide the text by default",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}
}

View File

@@ -347,6 +347,9 @@
"editItem": {
"message": "Edit Item"
},
"viewItem": {
"message": "View Item"
},
"ex": {
"message": "ex.",
"description": "Short abbreviation for 'example'."
@@ -790,9 +793,15 @@
"warning": {
"message": "Warning"
},
"confirmVaultExport": {
"message": "Confirm Vault Export"
},
"exportWarningDesc": {
"message": "This export contains your vault data in an unencrypted format. You should not store or send the exported file over unsecure channels (such as email). Delete it immediately after you are done using it."
},
"encExportWarningDesc": {
"message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file."
},
"exportMasterPassword": {
"message": "Enter your master password to export your vault data."
},
@@ -1659,6 +1668,9 @@
"paymentInformation": {
"message": "Payment Information"
},
"billingInformation": {
"message": "Billing Information"
},
"creditCard": {
"message": "Credit Card"
},
@@ -2483,6 +2495,15 @@
}
}
},
"unlinkedSsoUser": {
"message": "Unlinked SSO for user $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "John Smith"
}
}
},
"device": {
"message": "Device"
},
@@ -2786,6 +2807,9 @@
"updateEncryptionKeyWarning": {
"message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed."
},
"updateEncryptionKeyExportWarning": {
"message": "Any encrypted exports that you have saved will also become invalid."
},
"subscription": {
"message": "Subscription"
},
@@ -3295,15 +3319,35 @@
"deletionDate": {
"message": "Deletion Date"
},
"deletionDateDesc": {
"message": "The Send will be permanently deleted on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"expirationDate": {
"message": "Expiration Date"
},
"expirationDateDesc": {
"message": "If set, access to this Send will expire on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"maxAccessCount": {
"message": "Maximum Access Count"
},
"maxAccessCountDesc": {
"message": "If set, users will no longer be able to access this send once the maximum access count is reached.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"currentAccessCount": {
"message": "Current Access Count"
},
"sendPasswordDesc": {
"message": "Optionally require a password for users to access this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendNotesDesc": {
"message": "Private notes about this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"disabled": {
"message": "Disabled"
},
@@ -3324,9 +3368,22 @@
"removePasswordConfirmation": {
"message": "Are you sure you want to remove the password?"
},
"disableThisSend": {
"message": "Disable this Send so that no one can access it.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"allSends": {
"message": "All Sends"
},
"maxAccessCountReached": {
"message": "Max access count reached"
},
"pendingDeletion": {
"message": "Pending deletion"
},
"expired": {
"message": "Expired"
},
"searchSends": {
"message": "Search Sends",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
@@ -3346,8 +3403,233 @@
"downloadFile": {
"message": "Download File"
},
"sendAccessUnavailable": {
"message": "The Send you are trying to access does not exist or is no longer available.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"noSendsInList": {
"message": "There are no Sends to list.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"emergencyAccess": {
"message": "Emergency Access"
},
"emergencyAccessDesc": {
"message": "Grant and manage emergency access for trusted contacts. Trusted contacts may request access to either View or Takeover your account in case of a emergency. Visit our help page for more information and details into how zero knowledge sharing works."
},
"trustedEmergencyContacts": {
"message": "Trusted emergency contacts"
},
"noTrustedContacts": {
"message": "You have not added any emergency contacts yet, invite a trusted contact to get started."
},
"addEmergencyContact": {
"message": "Add emergency contact"
},
"designatedEmergencyContacts": {
"message": "Designated as emergency contact"
},
"noGrantedAccess": {
"message": "You have not been designated as an emergency contact for anyone yet."
},
"inviteEmergencyContact": {
"message": "Invite emergency contact"
},
"editEmergencyContact": {
"message": "Edit emergency contact"
},
"inviteEmergencyContactDesc": {
"message": "Invite a new emergency contact by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account."
},
"emergencyAccessRecoveryInitiated": {
"message": "Emergency Access Initiated"
},
"emergencyAccessRecoveryApproved": {
"message": "Emergency Access Approved"
},
"viewDesc": {
"message": "Can view all items in your own vault."
},
"takeover": {
"message": "Takeover"
},
"takeoverDesc": {
"message": "Can reset your account with a new master password."
},
"waitTime": {
"message": "Wait Time"
},
"waitTimeDesc": {
"message": "Time required before automatically granting access."
},
"oneDay": {
"message": "1 day"
},
"days": {
"message": "$DAYS$ days",
"placeholders": {
"days": {
"content": "$1",
"example": "1"
}
}
},
"invitedUser": {
"message": "Invited user."
},
"acceptEmergencyAccess": {
"message": "You've been invited to become an emergency contact for the user listed above. To accept the invitation, you need to log in or create a new Bitwarden account."
},
"emergencyInviteAcceptFailed": {
"message": "Unable to accept invitation. Ask the user to send a new invitation."
},
"emergencyInviteAcceptFailedShort": {
"message": "Unable to accept invitation. $DESCRIPTION$",
"placeholders": {
"description": {
"content": "$1",
"example": "You must enable 2FA on your user account before you can join this organization."
}
}
},
"emergencyInviteAcceptedDesc": {
"message": "You can access the emergency options for this user after your identity has been confirmed. We'll send you an email when that happens."
},
"requestAccess": {
"message": "Request Access"
},
"requestAccessConfirmation": {
"message": "Are you sure you want to request emergency access? You will be provided access after $WAITTIME$ day(s) or whenever the user manually approves the request.",
"placeholders": {
"waittime": {
"content": "$1",
"example": "1"
}
}
},
"requestSent": {
"message": "Emergency access requested for $USER$. We'll notify you by email when it's possible to continue.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"approve": {
"message": "Approve"
},
"reject": {
"message": "Reject"
},
"approveAccessConfirmation": {
"message": "Are you sure you want to approve emergency access? This will allow $USER$ to $ACTION$ your account.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
},
"action": {
"content": "$2",
"example": "View"
}
}
},
"emergencyApproved": {
"message": "Emergency access approved."
},
"emergencyRejected": {
"message": "Emergency access rejected"
},
"passwordResetFor": {
"message": "Password reset for $USER$. You can now login using the new password.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"personalOwnership": {
"message": "Personal Ownership"
},
"personalOwnershipPolicyDesc": {
"message": "Require users to save vault items to an organization by removing the personal ownership option."
},
"personalOwnershipExemption": {
"message": "Organization Owners and Administrators are exempt from this policy's enforcement."
},
"personalOwnershipSubmitError": {
"message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available Collections."
},
"modifiedPolicyId": {
"message": "Modified policy $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "Master Password"
}
}
},
"planPrice": {
"message": "Plan price"
},
"estimatedTax": {
"message": "Estimated tax"
},
"custom": {
"message": "Custom"
},
"customDesc": {
"message": "Allows more granular control of user permissions for advanced configurations."
},
"permissions": {
"message": "Permissions"
},
"accessBusinessPortal": {
"message": "Access Business Portal"
},
"accessEventLogs": {
"message": "Access Event Logs"
},
"accessImportExport": {
"message": "Access Import/Export"
},
"accessReports": {
"message": "Access Reports"
},
"manageAllCollections": {
"message": "Manage All Collections"
},
"manageAssignedCollections": {
"message": "Manage Assigned Collections"
},
"manageGroups": {
"message": "Manage Groups"
},
"managePolicies": {
"message": "Manage Policies"
},
"manageSso": {
"message": "Manage SSO"
},
"manageUsers": {
"message": "Manage Users"
},
"disableRequireSsoError": {
"message": "You must manually disable the Single Sign-On Authentication policy before this policy can be disabled."
},
"personalOwnershipPolicyInEffect": {
"message": "An organization policy is affecting your ownership options."
},
"custom": {
"message": "Custom"
},
"personalOwnershipCheckboxDesc": {
"message": "Disable personal ownership for organization users"
},
"textHiddenByDefault": {
"message": "When accessing the Send, hide the text by default",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}
}

View File

@@ -347,6 +347,9 @@
"editItem": {
"message": "Edit item"
},
"viewItem": {
"message": "View Item"
},
"ex": {
"message": "e.g.",
"description": "Short abbreviation for 'example'."
@@ -461,7 +464,7 @@
"message": "Delete attachment"
},
"deleteItemConfirmation": {
"message": "Are you sure you want to delete this item?"
"message": "Do you really want to send to the bin?"
},
"deletedItem": {
"message": "Item sent to bin"
@@ -790,9 +793,15 @@
"warning": {
"message": "Warning"
},
"confirmVaultExport": {
"message": "Confirm Vault Export"
},
"exportWarningDesc": {
"message": "This export contains your vault data in an unencrypted format. You should not store or send the exported file over insecure channels (such as email). Delete it immediately after you are done using it."
},
"encExportWarningDesc": {
"message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file."
},
"exportMasterPassword": {
"message": "Enter your master password to export your vault data."
},
@@ -1659,6 +1668,9 @@
"paymentInformation": {
"message": "Payment information"
},
"billingInformation": {
"message": "Billing Information"
},
"creditCard": {
"message": "Credit card"
},
@@ -2483,6 +2495,15 @@
}
}
},
"unlinkedSsoUser": {
"message": "Unlinked SSO for user $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "John Smith"
}
}
},
"device": {
"message": "Device"
},
@@ -2786,6 +2807,9 @@
"updateEncryptionKeyWarning": {
"message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, although this may be delayed."
},
"updateEncryptionKeyExportWarning": {
"message": "Any encrypted exports that you have saved will also become invalid."
},
"subscription": {
"message": "Subscription"
},
@@ -3295,15 +3319,35 @@
"deletionDate": {
"message": "Deletion date"
},
"deletionDateDesc": {
"message": "The Send will be permanently deleted on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"expirationDate": {
"message": "Expiration date"
},
"expirationDateDesc": {
"message": "If set, access to this Send will expire on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"maxAccessCount": {
"message": "Maximum access count"
},
"maxAccessCountDesc": {
"message": "If set, users will no longer be able to access this send once the maximum access count is reached.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"currentAccessCount": {
"message": "Current access count"
},
"sendPasswordDesc": {
"message": "Optionally require a password for users to access this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendNotesDesc": {
"message": "Private notes about this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"disabled": {
"message": "Disabled"
},
@@ -3324,9 +3368,22 @@
"removePasswordConfirmation": {
"message": "Are you sure you want to remove the password?"
},
"disableThisSend": {
"message": "Disable this Send so that no one can access it.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"allSends": {
"message": "All sends"
},
"maxAccessCountReached": {
"message": "Max access count reached"
},
"pendingDeletion": {
"message": "Pending deletion"
},
"expired": {
"message": "Expired"
},
"searchSends": {
"message": "Search sends",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
@@ -3346,8 +3403,230 @@
"downloadFile": {
"message": "Download file"
},
"sendAccessUnavailable": {
"message": "The Send you are trying to access does not exist or is no longer available.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"noSendsInList": {
"message": "There are no sends to list.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"emergencyAccess": {
"message": "Emergency Access"
},
"emergencyAccessDesc": {
"message": "Grant and manage emergency access for trusted contacts. Trusted contacts may request access to either View or Takeover your account in case of a emergency. Visit our help page for more information and details into how zero knowledge sharing works."
},
"trustedEmergencyContacts": {
"message": "Trusted emergency contacts"
},
"noTrustedContacts": {
"message": "You have not added any emergency contacts yet, invite a trusted contact to get started."
},
"addEmergencyContact": {
"message": "Add emergency contact"
},
"designatedEmergencyContacts": {
"message": "Designated as emergency contact"
},
"noGrantedAccess": {
"message": "You have not been designated as an emergency contact for anyone yet."
},
"inviteEmergencyContact": {
"message": "Invite emergency contact"
},
"editEmergencyContact": {
"message": "Edit emergency contact"
},
"inviteEmergencyContactDesc": {
"message": "Invite a new emergency contact by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account."
},
"emergencyAccessRecoveryInitiated": {
"message": "Emergency Access Initiated"
},
"emergencyAccessRecoveryApproved": {
"message": "Emergency Access Approved"
},
"viewDesc": {
"message": "Can view all items in your own vault."
},
"takeover": {
"message": "Takeover"
},
"takeoverDesc": {
"message": "Can reset your account with a new master password."
},
"waitTime": {
"message": "Wait Time"
},
"waitTimeDesc": {
"message": "Time required before automatically granting access."
},
"oneDay": {
"message": "1 day"
},
"days": {
"message": "$DAYS$ days",
"placeholders": {
"days": {
"content": "$1",
"example": "1"
}
}
},
"invitedUser": {
"message": "Invited user."
},
"acceptEmergencyAccess": {
"message": "You've been invited to become an emergency contact for the user listed above. To accept the invitation, you need to log in or create a new Bitwarden account."
},
"emergencyInviteAcceptFailed": {
"message": "Unable to accept invitation. Ask the user to send a new invitation."
},
"emergencyInviteAcceptFailedShort": {
"message": "Unable to accept invitation. $DESCRIPTION$",
"placeholders": {
"description": {
"content": "$1",
"example": "You must enable 2FA on your user account before you can join this organization."
}
}
},
"emergencyInviteAcceptedDesc": {
"message": "You can access the emergency options for this user after your identity has been confirmed. We'll send you an email when that happens."
},
"requestAccess": {
"message": "Request Access"
},
"requestAccessConfirmation": {
"message": "Are you sure you want to request emergency access? You will be provided access after $WAITTIME$ day(s) or whenever the user manually approves the request.",
"placeholders": {
"waittime": {
"content": "$1",
"example": "1"
}
}
},
"requestSent": {
"message": "Emergency access requested for $USER$. We'll notify you by email when it's possible to continue.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"approve": {
"message": "Approve"
},
"reject": {
"message": "Reject"
},
"approveAccessConfirmation": {
"message": "Are you sure you want to approve emergency access? This will allow $USER$ to $ACTION$ your account.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
},
"action": {
"content": "$2",
"example": "View"
}
}
},
"emergencyApproved": {
"message": "Emergency access approved."
},
"emergencyRejected": {
"message": "Emergency access rejected"
},
"passwordResetFor": {
"message": "Password reset for $USER$. You can now login using the new password.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"personalOwnership": {
"message": "Personal Ownership"
},
"personalOwnershipPolicyDesc": {
"message": "Require users to save vault items to an organization by removing the personal ownership option."
},
"personalOwnershipExemption": {
"message": "Organization Owners and Administrators are exempt from this policy's enforcement."
},
"personalOwnershipSubmitError": {
"message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available Collections."
},
"modifiedPolicyId": {
"message": "Modified policy $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "Master Password"
}
}
},
"planPrice": {
"message": "Plan price"
},
"estimatedTax": {
"message": "Estimated tax"
},
"custom": {
"message": "Custom"
},
"customDesc": {
"message": "Allows more granular control of user permissions for advanced configurations."
},
"permissions": {
"message": "Permissions"
},
"accessBusinessPortal": {
"message": "Access Business Portal"
},
"accessEventLogs": {
"message": "Access Event Logs"
},
"accessImportExport": {
"message": "Access Import/Export"
},
"accessReports": {
"message": "Access Reports"
},
"manageAllCollections": {
"message": "Manage All Collections"
},
"manageAssignedCollections": {
"message": "Manage Assigned Collections"
},
"manageGroups": {
"message": "Manage Groups"
},
"managePolicies": {
"message": "Manage Policies"
},
"manageSso": {
"message": "Manage SSO"
},
"manageUsers": {
"message": "Manage Users"
},
"disableRequireSsoError": {
"message": "You must manually disable the Single Sign-On Authentication policy before this policy can be disabled."
},
"personalOwnershipPolicyInEffect": {
"message": "An organization policy is affecting your ownership options."
},
"personalOwnershipCheckboxDesc": {
"message": "Disable personal ownership for organization users"
},
"textHiddenByDefault": {
"message": "When accessing the Send, hide the text by default",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}
}

View File

@@ -347,6 +347,9 @@
"editItem": {
"message": "Edit item"
},
"viewItem": {
"message": "View Item"
},
"ex": {
"message": "e.g.",
"description": "Short abbreviation for 'example'."
@@ -790,9 +793,15 @@
"warning": {
"message": "Warning"
},
"confirmVaultExport": {
"message": "Confirm Vault Export"
},
"exportWarningDesc": {
"message": "This export contains your vault data in an unencrypted format. You should not store or send the exported file over insecure channels (such as email). Delete it immediately after you are done using it."
},
"encExportWarningDesc": {
"message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file."
},
"exportMasterPassword": {
"message": "Enter your master password to export your vault data."
},
@@ -1659,6 +1668,9 @@
"paymentInformation": {
"message": "Payment information"
},
"billingInformation": {
"message": "Billing Information"
},
"creditCard": {
"message": "Credit card"
},
@@ -2483,6 +2495,15 @@
}
}
},
"unlinkedSsoUser": {
"message": "Unlinked SSO for user $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "John Smith"
}
}
},
"device": {
"message": "Device"
},
@@ -2786,6 +2807,9 @@
"updateEncryptionKeyWarning": {
"message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, although this may be delayed."
},
"updateEncryptionKeyExportWarning": {
"message": "Any encrypted exports that you have saved will also become invalid."
},
"subscription": {
"message": "Subscription"
},
@@ -3295,15 +3319,35 @@
"deletionDate": {
"message": "Deletion Date"
},
"deletionDateDesc": {
"message": "The Send will be permanently deleted on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"expirationDate": {
"message": "Expiration Date"
},
"expirationDateDesc": {
"message": "If set, access to this Send will expire on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"maxAccessCount": {
"message": "Maximum Access Count"
},
"maxAccessCountDesc": {
"message": "If set, users will no longer be able to access this send once the maximum access count is reached.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"currentAccessCount": {
"message": "Current Access Count"
},
"sendPasswordDesc": {
"message": "Optionally require a password for users to access this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendNotesDesc": {
"message": "Private notes about this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"disabled": {
"message": "Disabled"
},
@@ -3324,9 +3368,22 @@
"removePasswordConfirmation": {
"message": "Are you sure you want to remove the password?"
},
"disableThisSend": {
"message": "Disable this Send so that no one can access it.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"allSends": {
"message": "All Sends"
},
"maxAccessCountReached": {
"message": "Max access count reached"
},
"pendingDeletion": {
"message": "Pending deletion"
},
"expired": {
"message": "Expired"
},
"searchSends": {
"message": "Search Sends",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
@@ -3346,8 +3403,230 @@
"downloadFile": {
"message": "Download File"
},
"sendAccessUnavailable": {
"message": "The Send you are trying to access does not exist or is no longer available.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"noSendsInList": {
"message": "There are no Sends to list.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"emergencyAccess": {
"message": "Emergency Access"
},
"emergencyAccessDesc": {
"message": "Grant and manage emergency access for trusted contacts. Trusted contacts may request access to either View or Takeover your account in case of a emergency. Visit our help page for more information and details into how zero knowledge sharing works."
},
"trustedEmergencyContacts": {
"message": "Trusted emergency contacts"
},
"noTrustedContacts": {
"message": "You have not added any emergency contacts yet, invite a trusted contact to get started."
},
"addEmergencyContact": {
"message": "Add emergency contact"
},
"designatedEmergencyContacts": {
"message": "Designated as emergency contact"
},
"noGrantedAccess": {
"message": "You have not been designated as an emergency contact for anyone yet."
},
"inviteEmergencyContact": {
"message": "Invite emergency contact"
},
"editEmergencyContact": {
"message": "Edit emergency contact"
},
"inviteEmergencyContactDesc": {
"message": "Invite a new emergency contact by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account."
},
"emergencyAccessRecoveryInitiated": {
"message": "Emergency Access Initiated"
},
"emergencyAccessRecoveryApproved": {
"message": "Emergency Access Approved"
},
"viewDesc": {
"message": "Can view all items in your own vault."
},
"takeover": {
"message": "Takeover"
},
"takeoverDesc": {
"message": "Can reset your account with a new master password."
},
"waitTime": {
"message": "Wait Time"
},
"waitTimeDesc": {
"message": "Time required before automatically granting access."
},
"oneDay": {
"message": "1 day"
},
"days": {
"message": "$DAYS$ days",
"placeholders": {
"days": {
"content": "$1",
"example": "1"
}
}
},
"invitedUser": {
"message": "Invited user."
},
"acceptEmergencyAccess": {
"message": "You've been invited to become an emergency contact for the user listed above. To accept the invitation, you need to log in or create a new Bitwarden account."
},
"emergencyInviteAcceptFailed": {
"message": "Unable to accept invitation. Ask the user to send a new invitation."
},
"emergencyInviteAcceptFailedShort": {
"message": "Unable to accept invitation. $DESCRIPTION$",
"placeholders": {
"description": {
"content": "$1",
"example": "You must enable 2FA on your user account before you can join this organization."
}
}
},
"emergencyInviteAcceptedDesc": {
"message": "You can access the emergency options for this user after your identity has been confirmed. We'll send you an email when that happens."
},
"requestAccess": {
"message": "Request Access"
},
"requestAccessConfirmation": {
"message": "Are you sure you want to request emergency access? You will be provided access after $WAITTIME$ day(s) or whenever the user manually approves the request.",
"placeholders": {
"waittime": {
"content": "$1",
"example": "1"
}
}
},
"requestSent": {
"message": "Emergency access requested for $USER$. We'll notify you by email when it's possible to continue.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"approve": {
"message": "Approve"
},
"reject": {
"message": "Reject"
},
"approveAccessConfirmation": {
"message": "Are you sure you want to approve emergency access? This will allow $USER$ to $ACTION$ your account.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
},
"action": {
"content": "$2",
"example": "View"
}
}
},
"emergencyApproved": {
"message": "Emergency access approved."
},
"emergencyRejected": {
"message": "Emergency access rejected"
},
"passwordResetFor": {
"message": "Password reset for $USER$. You can now login using the new password.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"personalOwnership": {
"message": "Personal Ownership"
},
"personalOwnershipPolicyDesc": {
"message": "Require users to save vault items to an organization by removing the personal ownership option."
},
"personalOwnershipExemption": {
"message": "Organization Owners and Administrators are exempt from this policy's enforcement."
},
"personalOwnershipSubmitError": {
"message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available Collections."
},
"modifiedPolicyId": {
"message": "Modified policy $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "Master Password"
}
}
},
"planPrice": {
"message": "Plan price"
},
"estimatedTax": {
"message": "Estimated tax"
},
"custom": {
"message": "Custom"
},
"customDesc": {
"message": "Allows more granular control of user permissions for advanced configurations."
},
"permissions": {
"message": "Permissions"
},
"accessBusinessPortal": {
"message": "Access Business Portal"
},
"accessEventLogs": {
"message": "Access Event Logs"
},
"accessImportExport": {
"message": "Access Import/Export"
},
"accessReports": {
"message": "Access Reports"
},
"manageAllCollections": {
"message": "Manage All Collections"
},
"manageAssignedCollections": {
"message": "Manage Assigned Collections"
},
"manageGroups": {
"message": "Manage Groups"
},
"managePolicies": {
"message": "Manage Policies"
},
"manageSso": {
"message": "Manage SSO"
},
"manageUsers": {
"message": "Manage Users"
},
"disableRequireSsoError": {
"message": "You must manually disable the Single Sign-On Authentication policy before this policy can be disabled."
},
"personalOwnershipPolicyInEffect": {
"message": "An organization policy is affecting your ownership options."
},
"personalOwnershipCheckboxDesc": {
"message": "Disable personal ownership for organization users"
},
"textHiddenByDefault": {
"message": "When accessing the Send, hide the text by default",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}
}

View File

@@ -347,6 +347,9 @@
"editItem": {
"message": "Edit Item"
},
"viewItem": {
"message": "View Item"
},
"ex": {
"message": "ekx.",
"description": "Short abbreviation for 'example'."
@@ -790,9 +793,15 @@
"warning": {
"message": "Warning"
},
"confirmVaultExport": {
"message": "Confirm Vault Export"
},
"exportWarningDesc": {
"message": "This export contains your vault data in an unencrypted format. You should not store or send the exported file over unsecure channels (such as email). Delete it immediately after you are done using it."
},
"encExportWarningDesc": {
"message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file."
},
"exportMasterPassword": {
"message": "Enter your master password to export your vault data."
},
@@ -1659,6 +1668,9 @@
"paymentInformation": {
"message": "Informoj pri Pago"
},
"billingInformation": {
"message": "Billing Information"
},
"creditCard": {
"message": "Credit Card"
},
@@ -2483,6 +2495,15 @@
}
}
},
"unlinkedSsoUser": {
"message": "Unlinked SSO for user $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "John Smith"
}
}
},
"device": {
"message": "Aparato"
},
@@ -2786,6 +2807,9 @@
"updateEncryptionKeyWarning": {
"message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed."
},
"updateEncryptionKeyExportWarning": {
"message": "Any encrypted exports that you have saved will also become invalid."
},
"subscription": {
"message": "Abono"
},
@@ -3295,15 +3319,35 @@
"deletionDate": {
"message": "Deletion Date"
},
"deletionDateDesc": {
"message": "The Send will be permanently deleted on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"expirationDate": {
"message": "Expiration Date"
},
"expirationDateDesc": {
"message": "If set, access to this Send will expire on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"maxAccessCount": {
"message": "Maximum Access Count"
},
"maxAccessCountDesc": {
"message": "If set, users will no longer be able to access this send once the maximum access count is reached.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"currentAccessCount": {
"message": "Current Access Count"
},
"sendPasswordDesc": {
"message": "Optionally require a password for users to access this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendNotesDesc": {
"message": "Private notes about this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"disabled": {
"message": "Disabled"
},
@@ -3324,9 +3368,22 @@
"removePasswordConfirmation": {
"message": "Are you sure you want to remove the password?"
},
"disableThisSend": {
"message": "Disable this Send so that no one can access it.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"allSends": {
"message": "All Sends"
},
"maxAccessCountReached": {
"message": "Max access count reached"
},
"pendingDeletion": {
"message": "Pending deletion"
},
"expired": {
"message": "Expired"
},
"searchSends": {
"message": "Search Sends",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
@@ -3346,8 +3403,230 @@
"downloadFile": {
"message": "Download File"
},
"sendAccessUnavailable": {
"message": "The Send you are trying to access does not exist or is no longer available.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"noSendsInList": {
"message": "There are no Sends to list.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"emergencyAccess": {
"message": "Emergency Access"
},
"emergencyAccessDesc": {
"message": "Grant and manage emergency access for trusted contacts. Trusted contacts may request access to either View or Takeover your account in case of a emergency. Visit our help page for more information and details into how zero knowledge sharing works."
},
"trustedEmergencyContacts": {
"message": "Trusted emergency contacts"
},
"noTrustedContacts": {
"message": "You have not added any emergency contacts yet, invite a trusted contact to get started."
},
"addEmergencyContact": {
"message": "Add emergency contact"
},
"designatedEmergencyContacts": {
"message": "Designated as emergency contact"
},
"noGrantedAccess": {
"message": "You have not been designated as an emergency contact for anyone yet."
},
"inviteEmergencyContact": {
"message": "Invite emergency contact"
},
"editEmergencyContact": {
"message": "Edit emergency contact"
},
"inviteEmergencyContactDesc": {
"message": "Invite a new emergency contact by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account."
},
"emergencyAccessRecoveryInitiated": {
"message": "Emergency Access Initiated"
},
"emergencyAccessRecoveryApproved": {
"message": "Emergency Access Approved"
},
"viewDesc": {
"message": "Can view all items in your own vault."
},
"takeover": {
"message": "Takeover"
},
"takeoverDesc": {
"message": "Can reset your account with a new master password."
},
"waitTime": {
"message": "Wait Time"
},
"waitTimeDesc": {
"message": "Time required before automatically granting access."
},
"oneDay": {
"message": "1 day"
},
"days": {
"message": "$DAYS$ days",
"placeholders": {
"days": {
"content": "$1",
"example": "1"
}
}
},
"invitedUser": {
"message": "Invited user."
},
"acceptEmergencyAccess": {
"message": "You've been invited to become an emergency contact for the user listed above. To accept the invitation, you need to log in or create a new Bitwarden account."
},
"emergencyInviteAcceptFailed": {
"message": "Unable to accept invitation. Ask the user to send a new invitation."
},
"emergencyInviteAcceptFailedShort": {
"message": "Unable to accept invitation. $DESCRIPTION$",
"placeholders": {
"description": {
"content": "$1",
"example": "You must enable 2FA on your user account before you can join this organization."
}
}
},
"emergencyInviteAcceptedDesc": {
"message": "You can access the emergency options for this user after your identity has been confirmed. We'll send you an email when that happens."
},
"requestAccess": {
"message": "Request Access"
},
"requestAccessConfirmation": {
"message": "Are you sure you want to request emergency access? You will be provided access after $WAITTIME$ day(s) or whenever the user manually approves the request.",
"placeholders": {
"waittime": {
"content": "$1",
"example": "1"
}
}
},
"requestSent": {
"message": "Emergency access requested for $USER$. We'll notify you by email when it's possible to continue.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"approve": {
"message": "Approve"
},
"reject": {
"message": "Reject"
},
"approveAccessConfirmation": {
"message": "Are you sure you want to approve emergency access? This will allow $USER$ to $ACTION$ your account.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
},
"action": {
"content": "$2",
"example": "View"
}
}
},
"emergencyApproved": {
"message": "Emergency access approved."
},
"emergencyRejected": {
"message": "Emergency access rejected"
},
"passwordResetFor": {
"message": "Password reset for $USER$. You can now login using the new password.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"personalOwnership": {
"message": "Personal Ownership"
},
"personalOwnershipPolicyDesc": {
"message": "Require users to save vault items to an organization by removing the personal ownership option."
},
"personalOwnershipExemption": {
"message": "Organization Owners and Administrators are exempt from this policy's enforcement."
},
"personalOwnershipSubmitError": {
"message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available Collections."
},
"modifiedPolicyId": {
"message": "Modified policy $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "Master Password"
}
}
},
"planPrice": {
"message": "Plan price"
},
"estimatedTax": {
"message": "Estimated tax"
},
"custom": {
"message": "Custom"
},
"customDesc": {
"message": "Allows more granular control of user permissions for advanced configurations."
},
"permissions": {
"message": "Permissions"
},
"accessBusinessPortal": {
"message": "Access Business Portal"
},
"accessEventLogs": {
"message": "Access Event Logs"
},
"accessImportExport": {
"message": "Access Import/Export"
},
"accessReports": {
"message": "Access Reports"
},
"manageAllCollections": {
"message": "Manage All Collections"
},
"manageAssignedCollections": {
"message": "Manage Assigned Collections"
},
"manageGroups": {
"message": "Manage Groups"
},
"managePolicies": {
"message": "Manage Policies"
},
"manageSso": {
"message": "Manage SSO"
},
"manageUsers": {
"message": "Manage Users"
},
"disableRequireSsoError": {
"message": "You must manually disable the Single Sign-On Authentication policy before this policy can be disabled."
},
"personalOwnershipPolicyInEffect": {
"message": "An organization policy is affecting your ownership options."
},
"personalOwnershipCheckboxDesc": {
"message": "Disable personal ownership for organization users"
},
"textHiddenByDefault": {
"message": "When accessing the Send, hide the text by default",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}
}

View File

@@ -347,6 +347,9 @@
"editItem": {
"message": "Editar elemento"
},
"viewItem": {
"message": "View Item"
},
"ex": {
"message": "ej.",
"description": "Short abbreviation for 'example'."
@@ -790,9 +793,15 @@
"warning": {
"message": "Advertencia"
},
"confirmVaultExport": {
"message": "Confirm Vault Export"
},
"exportWarningDesc": {
"message": "Esta exportación contiene los datos de tu caja fuerte en un formato no cifrado. No deberías almacenar o enviar el archivo exportado por canales no seguros (como el correo electrónico). Elimínalo inmediatamente cuando termines de utilizarlo."
},
"encExportWarningDesc": {
"message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file."
},
"exportMasterPassword": {
"message": "Introduce tu contraseña maestra para exportar la información de tu caja fuerte."
},
@@ -1659,6 +1668,9 @@
"paymentInformation": {
"message": "Información de pago"
},
"billingInformation": {
"message": "Billing Information"
},
"creditCard": {
"message": "Tarjeta de crédito"
},
@@ -2483,6 +2495,15 @@
}
}
},
"unlinkedSsoUser": {
"message": "Unlinked SSO for user $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "John Smith"
}
}
},
"device": {
"message": "Dispositivo"
},
@@ -2786,6 +2807,9 @@
"updateEncryptionKeyWarning": {
"message": "Una vez actualices tu clave de cifrado, será necesario que cierres sesión y vuelvas a identificarte en todas las aplicaciones de Bitwarden que estés utilizando (como la aplicación móvil o la extensión de navegador). Si la reautenticación falla (la cual descargaría la nueva clave de cifrad) puede producirse corrupción de datos. Intentaremos cerrar tu sesión automáticamente, pero puede tardar un tiempo."
},
"updateEncryptionKeyExportWarning": {
"message": "Any encrypted exports that you have saved will also become invalid."
},
"subscription": {
"message": "Suscripción"
},
@@ -3194,7 +3218,7 @@
"message": "Inicio de sesión único empresarial"
},
"ssoHandOff": {
"message": "You may now close this tab and continue in the extension."
"message": "Ya puedes cerrar esta pestaña y continuar en la extensión."
},
"businessPortal": {
"message": "Portal de negocios",
@@ -3295,15 +3319,35 @@
"deletionDate": {
"message": "Fecha de eliminación"
},
"deletionDateDesc": {
"message": "The Send will be permanently deleted on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"expirationDate": {
"message": "Fecha de Expiración"
},
"expirationDateDesc": {
"message": "If set, access to this Send will expire on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"maxAccessCount": {
"message": "Número máximo de accesos"
},
"maxAccessCountDesc": {
"message": "If set, users will no longer be able to access this send once the maximum access count is reached.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"currentAccessCount": {
"message": "Número de accesos actuales"
},
"sendPasswordDesc": {
"message": "Optionally require a password for users to access this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendNotesDesc": {
"message": "Private notes about this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"disabled": {
"message": "Deshabilitado"
},
@@ -3324,9 +3368,22 @@
"removePasswordConfirmation": {
"message": "¿Está seguro que desea eliminar la contraseña?"
},
"disableThisSend": {
"message": "Disable this Send so that no one can access it.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"allSends": {
"message": "Todos los Sends"
},
"maxAccessCountReached": {
"message": "Max access count reached"
},
"pendingDeletion": {
"message": "Pending deletion"
},
"expired": {
"message": "Expired"
},
"searchSends": {
"message": "Buscar Sends",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
@@ -3346,8 +3403,230 @@
"downloadFile": {
"message": "Descargar archivo"
},
"sendAccessUnavailable": {
"message": "The Send you are trying to access does not exist or is no longer available.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"noSendsInList": {
"message": "No hay Sends que listar.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"emergencyAccess": {
"message": "Emergency Access"
},
"emergencyAccessDesc": {
"message": "Grant and manage emergency access for trusted contacts. Trusted contacts may request access to either View or Takeover your account in case of a emergency. Visit our help page for more information and details into how zero knowledge sharing works."
},
"trustedEmergencyContacts": {
"message": "Trusted emergency contacts"
},
"noTrustedContacts": {
"message": "You have not added any emergency contacts yet, invite a trusted contact to get started."
},
"addEmergencyContact": {
"message": "Add emergency contact"
},
"designatedEmergencyContacts": {
"message": "Designated as emergency contact"
},
"noGrantedAccess": {
"message": "You have not been designated as an emergency contact for anyone yet."
},
"inviteEmergencyContact": {
"message": "Invite emergency contact"
},
"editEmergencyContact": {
"message": "Edit emergency contact"
},
"inviteEmergencyContactDesc": {
"message": "Invite a new emergency contact by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account."
},
"emergencyAccessRecoveryInitiated": {
"message": "Emergency Access Initiated"
},
"emergencyAccessRecoveryApproved": {
"message": "Emergency Access Approved"
},
"viewDesc": {
"message": "Can view all items in your own vault."
},
"takeover": {
"message": "Takeover"
},
"takeoverDesc": {
"message": "Can reset your account with a new master password."
},
"waitTime": {
"message": "Wait Time"
},
"waitTimeDesc": {
"message": "Time required before automatically granting access."
},
"oneDay": {
"message": "1 day"
},
"days": {
"message": "$DAYS$ days",
"placeholders": {
"days": {
"content": "$1",
"example": "1"
}
}
},
"invitedUser": {
"message": "Invited user."
},
"acceptEmergencyAccess": {
"message": "You've been invited to become an emergency contact for the user listed above. To accept the invitation, you need to log in or create a new Bitwarden account."
},
"emergencyInviteAcceptFailed": {
"message": "Unable to accept invitation. Ask the user to send a new invitation."
},
"emergencyInviteAcceptFailedShort": {
"message": "Unable to accept invitation. $DESCRIPTION$",
"placeholders": {
"description": {
"content": "$1",
"example": "You must enable 2FA on your user account before you can join this organization."
}
}
},
"emergencyInviteAcceptedDesc": {
"message": "You can access the emergency options for this user after your identity has been confirmed. We'll send you an email when that happens."
},
"requestAccess": {
"message": "Request Access"
},
"requestAccessConfirmation": {
"message": "Are you sure you want to request emergency access? You will be provided access after $WAITTIME$ day(s) or whenever the user manually approves the request.",
"placeholders": {
"waittime": {
"content": "$1",
"example": "1"
}
}
},
"requestSent": {
"message": "Emergency access requested for $USER$. We'll notify you by email when it's possible to continue.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"approve": {
"message": "Approve"
},
"reject": {
"message": "Reject"
},
"approveAccessConfirmation": {
"message": "Are you sure you want to approve emergency access? This will allow $USER$ to $ACTION$ your account.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
},
"action": {
"content": "$2",
"example": "View"
}
}
},
"emergencyApproved": {
"message": "Emergency access approved."
},
"emergencyRejected": {
"message": "Emergency access rejected"
},
"passwordResetFor": {
"message": "Password reset for $USER$. You can now login using the new password.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"personalOwnership": {
"message": "Personal Ownership"
},
"personalOwnershipPolicyDesc": {
"message": "Require users to save vault items to an organization by removing the personal ownership option."
},
"personalOwnershipExemption": {
"message": "Organization Owners and Administrators are exempt from this policy's enforcement."
},
"personalOwnershipSubmitError": {
"message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available Collections."
},
"modifiedPolicyId": {
"message": "Modified policy $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "Master Password"
}
}
},
"planPrice": {
"message": "Plan price"
},
"estimatedTax": {
"message": "Estimated tax"
},
"custom": {
"message": "Custom"
},
"customDesc": {
"message": "Allows more granular control of user permissions for advanced configurations."
},
"permissions": {
"message": "Permissions"
},
"accessBusinessPortal": {
"message": "Access Business Portal"
},
"accessEventLogs": {
"message": "Access Event Logs"
},
"accessImportExport": {
"message": "Access Import/Export"
},
"accessReports": {
"message": "Access Reports"
},
"manageAllCollections": {
"message": "Manage All Collections"
},
"manageAssignedCollections": {
"message": "Manage Assigned Collections"
},
"manageGroups": {
"message": "Manage Groups"
},
"managePolicies": {
"message": "Manage Policies"
},
"manageSso": {
"message": "Manage SSO"
},
"manageUsers": {
"message": "Manage Users"
},
"disableRequireSsoError": {
"message": "You must manually disable the Single Sign-On Authentication policy before this policy can be disabled."
},
"personalOwnershipPolicyInEffect": {
"message": "An organization policy is affecting your ownership options."
},
"personalOwnershipCheckboxDesc": {
"message": "Disable personal ownership for organization users"
},
"textHiddenByDefault": {
"message": "When accessing the Send, hide the text by default",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}
}

View File

@@ -347,6 +347,9 @@
"editItem": {
"message": "Kirje muutmine"
},
"viewItem": {
"message": "View Item"
},
"ex": {
"message": "nt.",
"description": "Short abbreviation for 'example'."
@@ -790,9 +793,15 @@
"warning": {
"message": "Hoiatus"
},
"confirmVaultExport": {
"message": "Confirm Vault Export"
},
"exportWarningDesc": {
"message": "Eksporditav fail on krüpteeringuta ja sisaldab hoidla sisu. Seda faili ei tohiks kaua käidelda ning mitte mingil juhul ebaturvaliselt saata (näiteks e-postiga). Kustuta see koheselt pärast kasutamist."
},
"encExportWarningDesc": {
"message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file."
},
"exportMasterPassword": {
"message": "Hoidlas olevate andmete eksportimiseks on vajalik ülemparooli sisestamine."
},
@@ -1659,6 +1668,9 @@
"paymentInformation": {
"message": "Maksemeetod"
},
"billingInformation": {
"message": "Billing Information"
},
"creditCard": {
"message": "Krediitkaart"
},
@@ -2483,6 +2495,15 @@
}
}
},
"unlinkedSsoUser": {
"message": "Unlinked SSO for user $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "John Smith"
}
}
},
"device": {
"message": "Seade"
},
@@ -2786,6 +2807,9 @@
"updateEncryptionKeyWarning": {
"message": "Pärast krüpteerimisvõtme uuendamist pead kõikides seadmetes, kus Bitwardeni rakendust kasutad, oma kontosse uuesti sisse logima (nt nutitelefonis ja brauseris). Välja- ja sisselogimise (mis ühtlasi laadib ka uue krüpteerimisvõtme) nurjumine võib tingida andmete riknemise. Üritame sinu seadmetest ise välja logida, aga see võib võtta natukene aega."
},
"updateEncryptionKeyExportWarning": {
"message": "Any encrypted exports that you have saved will also become invalid."
},
"subscription": {
"message": "Tellimus"
},
@@ -3194,7 +3218,7 @@
"message": "Ettevõtte Single Sign-On"
},
"ssoHandOff": {
"message": "You may now close this tab and continue in the extension."
"message": "Võid nüüd selle vahelehe sulgeda ning jätkata brauseri laienduses."
},
"businessPortal": {
"message": "Ärikliendi portaal",
@@ -3295,15 +3319,35 @@
"deletionDate": {
"message": "Kustutamise kuupäev"
},
"deletionDateDesc": {
"message": "The Send will be permanently deleted on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"expirationDate": {
"message": "Aegumiskuupäev"
},
"expirationDateDesc": {
"message": "If set, access to this Send will expire on the specified date and time.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"maxAccessCount": {
"message": "Maksimaalne ligipääsude arv"
},
"maxAccessCountDesc": {
"message": "If set, users will no longer be able to access this send once the maximum access count is reached.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"currentAccessCount": {
"message": "Hetkeline ligipääsude arv"
},
"sendPasswordDesc": {
"message": "Optionally require a password for users to access this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendNotesDesc": {
"message": "Private notes about this Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"disabled": {
"message": "Keelatud"
},
@@ -3324,9 +3368,22 @@
"removePasswordConfirmation": {
"message": "Soovid kindlasti selle parooli eemaldada?"
},
"disableThisSend": {
"message": "Disable this Send so that no one can access it.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"allSends": {
"message": "Kõik Sendid"
},
"maxAccessCountReached": {
"message": "Max access count reached"
},
"pendingDeletion": {
"message": "Pending deletion"
},
"expired": {
"message": "Expired"
},
"searchSends": {
"message": "Otsi Sende",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
@@ -3346,8 +3403,230 @@
"downloadFile": {
"message": "Laadi fail alla"
},
"sendAccessUnavailable": {
"message": "The Send you are trying to access does not exist or is no longer available.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"noSendsInList": {
"message": "Puuduvad Sendid, mida kuvada.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"emergencyAccess": {
"message": "Emergency Access"
},
"emergencyAccessDesc": {
"message": "Grant and manage emergency access for trusted contacts. Trusted contacts may request access to either View or Takeover your account in case of a emergency. Visit our help page for more information and details into how zero knowledge sharing works."
},
"trustedEmergencyContacts": {
"message": "Trusted emergency contacts"
},
"noTrustedContacts": {
"message": "You have not added any emergency contacts yet, invite a trusted contact to get started."
},
"addEmergencyContact": {
"message": "Add emergency contact"
},
"designatedEmergencyContacts": {
"message": "Designated as emergency contact"
},
"noGrantedAccess": {
"message": "You have not been designated as an emergency contact for anyone yet."
},
"inviteEmergencyContact": {
"message": "Invite emergency contact"
},
"editEmergencyContact": {
"message": "Edit emergency contact"
},
"inviteEmergencyContactDesc": {
"message": "Invite a new emergency contact by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account."
},
"emergencyAccessRecoveryInitiated": {
"message": "Emergency Access Initiated"
},
"emergencyAccessRecoveryApproved": {
"message": "Emergency Access Approved"
},
"viewDesc": {
"message": "Can view all items in your own vault."
},
"takeover": {
"message": "Takeover"
},
"takeoverDesc": {
"message": "Can reset your account with a new master password."
},
"waitTime": {
"message": "Wait Time"
},
"waitTimeDesc": {
"message": "Time required before automatically granting access."
},
"oneDay": {
"message": "1 day"
},
"days": {
"message": "$DAYS$ days",
"placeholders": {
"days": {
"content": "$1",
"example": "1"
}
}
},
"invitedUser": {
"message": "Invited user."
},
"acceptEmergencyAccess": {
"message": "You've been invited to become an emergency contact for the user listed above. To accept the invitation, you need to log in or create a new Bitwarden account."
},
"emergencyInviteAcceptFailed": {
"message": "Unable to accept invitation. Ask the user to send a new invitation."
},
"emergencyInviteAcceptFailedShort": {
"message": "Unable to accept invitation. $DESCRIPTION$",
"placeholders": {
"description": {
"content": "$1",
"example": "You must enable 2FA on your user account before you can join this organization."
}
}
},
"emergencyInviteAcceptedDesc": {
"message": "You can access the emergency options for this user after your identity has been confirmed. We'll send you an email when that happens."
},
"requestAccess": {
"message": "Request Access"
},
"requestAccessConfirmation": {
"message": "Are you sure you want to request emergency access? You will be provided access after $WAITTIME$ day(s) or whenever the user manually approves the request.",
"placeholders": {
"waittime": {
"content": "$1",
"example": "1"
}
}
},
"requestSent": {
"message": "Emergency access requested for $USER$. We'll notify you by email when it's possible to continue.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"approve": {
"message": "Approve"
},
"reject": {
"message": "Reject"
},
"approveAccessConfirmation": {
"message": "Are you sure you want to approve emergency access? This will allow $USER$ to $ACTION$ your account.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
},
"action": {
"content": "$2",
"example": "View"
}
}
},
"emergencyApproved": {
"message": "Emergency access approved."
},
"emergencyRejected": {
"message": "Emergency access rejected"
},
"passwordResetFor": {
"message": "Password reset for $USER$. You can now login using the new password.",
"placeholders": {
"user": {
"content": "$1",
"example": "John Smith"
}
}
},
"personalOwnership": {
"message": "Personal Ownership"
},
"personalOwnershipPolicyDesc": {
"message": "Require users to save vault items to an organization by removing the personal ownership option."
},
"personalOwnershipExemption": {
"message": "Organization Owners and Administrators are exempt from this policy's enforcement."
},
"personalOwnershipSubmitError": {
"message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available Collections."
},
"modifiedPolicyId": {
"message": "Modified policy $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "Master Password"
}
}
},
"planPrice": {
"message": "Plan price"
},
"estimatedTax": {
"message": "Estimated tax"
},
"custom": {
"message": "Custom"
},
"customDesc": {
"message": "Allows more granular control of user permissions for advanced configurations."
},
"permissions": {
"message": "Permissions"
},
"accessBusinessPortal": {
"message": "Access Business Portal"
},
"accessEventLogs": {
"message": "Access Event Logs"
},
"accessImportExport": {
"message": "Access Import/Export"
},
"accessReports": {
"message": "Access Reports"
},
"manageAllCollections": {
"message": "Manage All Collections"
},
"manageAssignedCollections": {
"message": "Manage Assigned Collections"
},
"manageGroups": {
"message": "Manage Groups"
},
"managePolicies": {
"message": "Manage Policies"
},
"manageSso": {
"message": "Manage SSO"
},
"manageUsers": {
"message": "Manage Users"
},
"disableRequireSsoError": {
"message": "You must manually disable the Single Sign-On Authentication policy before this policy can be disabled."
},
"personalOwnershipPolicyInEffect": {
"message": "An organization policy is affecting your ownership options."
},
"personalOwnershipCheckboxDesc": {
"message": "Disable personal ownership for organization users"
},
"textHiddenByDefault": {
"message": "When accessing the Send, hide the text by default",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}
}

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