mirror of
https://github.com/nextcloud/passman.git
synced 2025-10-11 14:06:07 +08:00
Lock vault after 3 wrong attempts (Fixes #197)
Fix share button, fix shared_key not added to storedCredential after sharing (Fixes #249) Add password app importer. Fixes #248 Fix version check via proxy. Fixes #237 Fix activity app not filtering. Fixes #246 Add EnPass txt importer. Fixes #159 Fix for disabled share button Require vault key for export. Fixes #199 Indicate that sharing only works with users that have 1 or more vaults. Fixes #242 Reset tags on logout. Fixes #245 Ability to enter OTP secret manually. Fixes #198 Create teampass importer. Fixes #244
This commit is contained in:
parent
76852c0d77
commit
6767322a22
40 changed files with 619 additions and 141 deletions
|
@ -8,7 +8,7 @@ To make it possible for us to help you please fill out below information careful
|
||||||
If you like to make a feature request, please remove the issue template.
|
If you like to make a feature request, please remove the issue template.
|
||||||
If you want to fill in a issue, remove the feature request template.
|
If you want to fill in a issue, remove the feature request template.
|
||||||
-->
|
-->
|
||||||
##Issue
|
## Bug report
|
||||||
|
|
||||||
### Steps to reproduce
|
### Steps to reproduce
|
||||||
1.
|
1.
|
||||||
|
@ -43,8 +43,6 @@ Tell us what happens instead
|
||||||
**cloud version:** (see admin page or version.php)
|
**cloud version:** (see admin page or version.php)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### Browser log
|
#### Browser log
|
||||||
<details>
|
<details>
|
||||||
<summary>Browser log</summary>
|
<summary>Browser log</summary>
|
||||||
|
|
133
README.md
133
README.md
|
@ -1,4 +1,5 @@
|
||||||
#Passman
|
#Passman
|
||||||
|
Passman is a full featured password manager.
|
||||||
|
|
||||||
[](https://travis-ci.org/nextcloud/passman)
|
[](https://travis-ci.org/nextcloud/passman)
|
||||||
[](https://www.codacy.com/app/brantje/passman?utm_source=github.com&utm_medium=referral&utm_content=nextcloud/passman&utm_campaign=Badge_Grade)
|
[](https://www.codacy.com/app/brantje/passman?utm_source=github.com&utm_medium=referral&utm_content=nextcloud/passman&utm_campaign=Badge_Grade)
|
||||||
|
@ -6,43 +7,19 @@
|
||||||
[](https://scrutinizer-ci.com/g/nextcloud/passman/?branch=master)
|
[](https://scrutinizer-ci.com/g/nextcloud/passman/?branch=master)
|
||||||
|
|
||||||
|
|
||||||
Passman is a full featured password manager.
|
## Contents
|
||||||
Features:
|
* [Screenshots](https://github.com/nextcloud/passman#Screenshots)
|
||||||
- Vaults
|
* [Features](https://github.com/nextcloud/passman#features)
|
||||||
- Vault key is never sent to the server
|
* [External apps](https://github.com/nextcloud/passman#external-apps)
|
||||||
- Credentials are stored with 256 bit AES
|
* [Security](https://github.com/nextcloud/passman#security)
|
||||||
- Ability to add custom fields to credentials
|
* [Password generation](https://github.com/nextcloud/passman#password-generation)
|
||||||
- Built-in OTP(One Time Password) generator
|
* [Storing credentials](https://github.com/nextcloud/passman#storing-credentials)
|
||||||
- Password analyzer
|
* [API](https://github.com/nextcloud/passman#api)
|
||||||
- Share passwords internally and via link in a secure manner.
|
* [Docker](https://github.com/nextcloud/passman#docker)
|
||||||
- Import from various password managers:
|
* [Maintainers](https://github.com/nextcloud/passman#main-developers)
|
||||||
- KeePass
|
* [Contributors](https://github.com/nextcloud/passman#contributors)
|
||||||
- LastPass
|
|
||||||
- DashLane
|
|
||||||
- ZOHO
|
|
||||||
- Clipperz.is
|
|
||||||
|
|
||||||
|
|
||||||
For a demo of this app visit [https://demo.passman.cc](https://demo.passman.cc)
|
|
||||||
|
|
||||||
## Tested on
|
|
||||||
- NextCloud 10 / 11
|
|
||||||
- ownCloud 9.1+
|
|
||||||
|
|
||||||
## Browser extensions
|
|
||||||
At this moment we don't have any extensions available, sorry!
|
|
||||||
However, if you're interested in helping us (or you want to guide us while building)
|
|
||||||
Check out: https://github.com/nextcloud/passman-chrome-extension
|
|
||||||
|
|
||||||
|
|
||||||
## Supported databases
|
|
||||||
- SQL Lite*
|
|
||||||
- MySQL / MariaDB*
|
|
||||||
|
|
||||||
*Tested on travis
|
|
||||||
|
|
||||||
Untested databases:
|
|
||||||
- pgsql
|
|
||||||
|
|
||||||
|
|
||||||
##Screenshots
|
##Screenshots
|
||||||
|
@ -56,15 +33,85 @@ Untested databases:
|
||||||
|
|
||||||
For more screenshots: [Click here](http://imgur.com/a/giKVt)
|
For more screenshots: [Click here](http://imgur.com/a/giKVt)
|
||||||
|
|
||||||
## Encryption (server side)
|
|
||||||
All passwords are encrypted client side AND server side.
|
## Features:
|
||||||
This means that if you move to another server you have to backup the following from config.php
|
- Vaults
|
||||||
- `passwordsalt`
|
- Vault key is never sent to the server
|
||||||
- `secret`
|
- Credentials are stored with 256 bit AES (see [security](https://github.com/nextcloud/passman#security))
|
||||||
|
- Ability to add custom fields to credentials
|
||||||
|
- Built-in OTP(One Time Password) generator
|
||||||
|
- Password analyzer
|
||||||
|
- Share passwords internally and via link in a secure manner.
|
||||||
|
- Import from various password managers:
|
||||||
|
- KeePass
|
||||||
|
- LastPass
|
||||||
|
- DashLane
|
||||||
|
- ZOHO
|
||||||
|
- Clipperz.is
|
||||||
|
- EnPass
|
||||||
|
- [ocPasswords](https://github.com/fcturner/passwords)
|
||||||
|
|
||||||
|
|
||||||
|
For a demo of this app visit [https://demo.passman.cc](https://demo.passman.cc)
|
||||||
|
|
||||||
|
## Tested on
|
||||||
|
- NextCloud 10 / 11
|
||||||
|
- ownCloud 9.1+
|
||||||
|
|
||||||
|
|
||||||
|
## External apps
|
||||||
|
- [Firefox / chrome extension](https://github.com/nextcloud/passman-webextension)
|
||||||
|
- [Android app](https://github.com/nextcloud/passman-android)
|
||||||
|
|
||||||
|
|
||||||
|
## Supported databases
|
||||||
|
- SQL Lite*
|
||||||
|
- MySQL / MariaDB*
|
||||||
|
|
||||||
|
*Tested on travis
|
||||||
|
|
||||||
|
Untested databases:
|
||||||
|
- pgsql
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
### Password generation
|
||||||
|
Passman features a build in password generator.
|
||||||
|
Not it only generates passwords, but it also measures their strength using [zxcvbn](https://github.com/dropbox/zxcvbn).
|
||||||
|

|
||||||
|
|
||||||
|
Generate passwords as you like
|
||||||
|

|
||||||
|
Passwords are generated using the random functions from `sjcl`.
|
||||||
|
|
||||||
|
|
||||||
|
### Storing credentials
|
||||||
|
All passwords are encrypted client side using [sjcl](https://github.com/bitwiseshiftleft/sjcl) which uses AES-256 bit.
|
||||||
|
Users supply a vault key which is feed into sjcl as encryption key.
|
||||||
|
After the credentials are encrypted they are send to the server, there they will be encrypted again.
|
||||||
|
This time using the following routine:
|
||||||
|
- A key is generated using `passwordsalt` and `secret` from config.php *so back those up*
|
||||||
|
- Then the key is [stretched](http://en.wikipedia.org/wiki/Key_stretching) using [Password-Based Key Derivation Function 2](http://en.wikipedia.org/wiki/PBKDF2) (PBKDF2).
|
||||||
|
- [Encrypt-then-MAC](http://en.wikipedia.org/wiki/Authenticated_encryption#Approaches_to_Authenticated_Encryption) (EtM) is used for ensuring the authenticity of the encrypted data.
|
||||||
|
- Uses openssl with the `aes-256-cbc` ciper.
|
||||||
|
- [Initialization vector](http://en.wikipedia.org/wiki/Initialization_vector) (IV) is hidden
|
||||||
|
- [Double Hash-based Message Authentication Code](http://en.wikipedia.org/wiki/Hash-based_message_authentication_code) (HMAC) is applied for verification of the source data.
|
||||||
|
|
||||||
|
|
||||||
|
### Sharing credentials.
|
||||||
|
Passman allows users to share passwords (this can be turned off by an administrator).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## API
|
||||||
|
For developers passman offers an [api](https://github.com/nextcloud/passman/wiki/API).
|
||||||
|
|
||||||
## Support Passman
|
## Support Passman
|
||||||
If you like passman, support us via [patreon](https://www.patreon.com/user?u=4833592) or [paypal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=6YS8F97PETVU2)
|
Passman is open source, but we would gladly accept a beer (or pizza!)
|
||||||
|
Please consider donating via
|
||||||
|
- [Paypal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=6YS8F97PETVU2)
|
||||||
|
- [Patreon](https://www.patreon.com/user?u=4833592)
|
||||||
|
- Bitcoin: 1H2c5tkGX54n48yEtM4Wm4UrAGTW85jQpe
|
||||||
|
|
||||||
## Code reviews
|
## Code reviews
|
||||||
If you have any improvements regarding our code.
|
If you have any improvements regarding our code.
|
||||||
|
@ -78,7 +125,7 @@ Or if you're feeling lazy, create an issue, and we'll think about it.
|
||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
To run passman with docker you can use `docker run -p 8080:80 -p 8443:443 brantje/passman`
|
To run passman with docker you can use `docker run -p 8080:80 -p 8443:443 brantje/passman`
|
||||||
To use your own SSL cert mount them as below
|
You have to supply your own ssl certs.
|
||||||
Example:
|
Example:
|
||||||
`docker run -p 8080:80 -p 8443:443 -v /directory/cert.pem:/data/ssl/cert.pem -v /directory/cert.key:/data/ssl/cert.key brantje/passman`
|
`docker run -p 8080:80 -p 8443:443 -v /directory/cert.pem:/data/ssl/cert.pem -v /directory/cert.key:/data/ssl/cert.key brantje/passman`
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ $manager->registerNotifier(function() {
|
||||||
$manager = \OC::$server->getActivityManager();
|
$manager = \OC::$server->getActivityManager();
|
||||||
$manager->registerExtension(function() {
|
$manager->registerExtension(function() {
|
||||||
return new Activity(
|
return new Activity(
|
||||||
\OC::$server->getL10NFactory()
|
\OC::$server->getURLGenerator()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ For an demo of this app visit [https://demo.passman.cc](https://demo.passman.cc)
|
||||||
]]></description>
|
]]></description>
|
||||||
|
|
||||||
<licence>AGPL</licence>
|
<licence>AGPL</licence>
|
||||||
<version>2.0.2</version>
|
<version>2.1.0</version>
|
||||||
<author homepage="https://github.com/brantje">Sander Brand</author>
|
<author homepage="https://github.com/brantje">Sander Brand</author>
|
||||||
<author homepage="https://github.com/animalillo">Marcos Zuriaga</author>
|
<author homepage="https://github.com/animalillo">Marcos Zuriaga</author>
|
||||||
<namespace>Passman</namespace>
|
<namespace>Passman</namespace>
|
||||||
|
|
|
@ -69,6 +69,7 @@ class TranslationController extends ApiController {
|
||||||
|
|
||||||
// js/app/controllers/import.js
|
// js/app/controllers/import.js
|
||||||
'import.file.read' => $this->trans->t('File read successfully!'),
|
'import.file.read' => $this->trans->t('File read successfully!'),
|
||||||
|
'import.steps' => $this->trans->t('Follow the following steps to import your file'),
|
||||||
|
|
||||||
'import.no.label' => $this->trans->t('Credential has no label, skipping'),
|
'import.no.label' => $this->trans->t('Credential has no label, skipping'),
|
||||||
'import.adding' => $this->trans->t('Adding {{credential}}'),
|
'import.adding' => $this->trans->t('Adding {{credential}}'),
|
||||||
|
@ -144,7 +145,7 @@ class TranslationController extends ApiController {
|
||||||
|
|
||||||
|
|
||||||
// templates/views/partials/edit_credential/otp.html
|
// templates/views/partials/edit_credential/otp.html
|
||||||
'upload.qr' => $this->trans->t('Upload your OTP qr code'),
|
'upload.qr' => $this->trans->t('Upload or enter your OTP secret'),
|
||||||
'current.qr' => $this->trans->t('Current OTP settings'),
|
'current.qr' => $this->trans->t('Current OTP settings'),
|
||||||
'issuer' => $this->trans->t('Issuer'),
|
'issuer' => $this->trans->t('Issuer'),
|
||||||
'secret' => $this->trans->t('Secret'),
|
'secret' => $this->trans->t('Secret'),
|
||||||
|
@ -173,6 +174,7 @@ class TranslationController extends ApiController {
|
||||||
// templates/views/partials/forms/settings/export.html
|
// templates/views/partials/forms/settings/export.html
|
||||||
'export.type' => $this->trans->t('Export type'),
|
'export.type' => $this->trans->t('Export type'),
|
||||||
'export' => $this->trans->t('Export'),
|
'export' => $this->trans->t('Export'),
|
||||||
|
'export.confirm.text' => $this->trans->t('Enter vault password to confirm export.'),
|
||||||
|
|
||||||
// templates/views/partials/forms/settings/general_settings.html
|
// templates/views/partials/forms/settings/general_settings.html
|
||||||
'rename.vault' => $this->trans->t('Rename vault'),
|
'rename.vault' => $this->trans->t('Rename vault'),
|
||||||
|
@ -226,6 +228,7 @@ class TranslationController extends ApiController {
|
||||||
|
|
||||||
// templates/vieuws/partials/forms/share_credential/basics.html
|
// templates/vieuws/partials/forms/share_credential/basics.html
|
||||||
'search.u.g' => $this->trans->t('Search users or groups...'),
|
'search.u.g' => $this->trans->t('Search users or groups...'),
|
||||||
|
'search.result.missing' => $this->trans->t('Missing users? Only users that have vaults are shown.'),
|
||||||
'cyphering' => $this->trans->t('Cyphering'),
|
'cyphering' => $this->trans->t('Cyphering'),
|
||||||
'uploading' => $this->trans->t('Uploading'),
|
'uploading' => $this->trans->t('Uploading'),
|
||||||
'user' => $this->trans->t('User'),
|
'user' => $this->trans->t('User'),
|
||||||
|
@ -325,6 +328,7 @@ class TranslationController extends ApiController {
|
||||||
'accept' => $this->trans->t('Accept'),
|
'accept' => $this->trans->t('Accept'),
|
||||||
'decline' => $this->trans->t('Decline'),
|
'decline' => $this->trans->t('Decline'),
|
||||||
'session.time.left' => $this->trans->t('You have {{session_time}} left before logout.'),
|
'session.time.left' => $this->trans->t('You have {{session_time}} left before logout.'),
|
||||||
|
'vault.locked' => $this->trans->t('Your vault has been locked for {{time}} because of {{tries}} failed attempts!'),
|
||||||
|
|
||||||
// templates/views/vaults.html
|
// templates/views/vaults.html
|
||||||
'last.access' => $this->trans->t('Last accessed'),
|
'last.access' => $this->trans->t('Last accessed'),
|
||||||
|
|
21
css/app.css
21
css/app.css
|
@ -326,6 +326,8 @@
|
||||||
display: inline-block; }
|
display: inline-block; }
|
||||||
.vault_wrapper .login_form .button {
|
.vault_wrapper .login_form .button {
|
||||||
margin-top: 10px; }
|
margin-top: 10px; }
|
||||||
|
.vault_wrapper .login_opts {
|
||||||
|
margin-bottom: 10px; }
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
.vault_wrapper {
|
.vault_wrapper {
|
||||||
|
@ -637,6 +639,8 @@
|
||||||
#app-content #app-content-wrapper .edit_credential .custom_fields table tr td, #app-content #app-content-wrapper .edit_credential .files table tr td {
|
#app-content #app-content-wrapper .edit_credential .custom_fields table tr td, #app-content #app-content-wrapper .edit_credential .files table tr td {
|
||||||
height: 50px;
|
height: 50px;
|
||||||
vertical-align: middle; }
|
vertical-align: middle; }
|
||||||
|
#app-content #app-content-wrapper .edit_credential .otpText {
|
||||||
|
padding-right: 10px; }
|
||||||
#app-content #app-content-wrapper .app_sidebar {
|
#app-content #app-content-wrapper .app_sidebar {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
overflow-y: auto; }
|
overflow-y: auto; }
|
||||||
|
@ -882,6 +886,12 @@
|
||||||
.tab_container.settings {
|
.tab_container.settings {
|
||||||
margin-bottom: 50px; }
|
margin-bottom: 50px; }
|
||||||
|
|
||||||
|
.import-steps {
|
||||||
|
padding-left: 16px;
|
||||||
|
margin-bottom: 10px; }
|
||||||
|
.import-steps li {
|
||||||
|
list-style-type: disc; }
|
||||||
|
|
||||||
#app-settings-content:not(.ng-hide) {
|
#app-settings-content:not(.ng-hide) {
|
||||||
height: 90px;
|
height: 90px;
|
||||||
display: inherit !important;
|
display: inherit !important;
|
||||||
|
@ -943,4 +953,15 @@
|
||||||
input[type="checkbox"] {
|
input[type="checkbox"] {
|
||||||
min-height: inherit; }
|
min-height: inherit; }
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 4px; }
|
||||||
|
|
||||||
|
.alert-danger {
|
||||||
|
color: #a94442;
|
||||||
|
background-color: #f2dede;
|
||||||
|
border-color: #ebccd1; }
|
||||||
|
|
||||||
/*# sourceMappingURL=app.css.map */
|
/*# sourceMappingURL=app.css.map */
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -72,7 +72,7 @@
|
||||||
url: 'views/partials/forms/edit_credential/basics.html',
|
url: 'views/partials/forms/edit_credential/basics.html',
|
||||||
color: 'blue'
|
color: 'blue'
|
||||||
};
|
};
|
||||||
|
$scope.otpType = 'qrcode';
|
||||||
$translate(['general', 'password', 'custom.fields','files','otp']).then(function (translations) {
|
$translate(['general', 'password', 'custom.fields','files','otp']).then(function (translations) {
|
||||||
$scope.tabs = [{
|
$scope.tabs = [{
|
||||||
title: translations.general,
|
title: translations.general,
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
.controller('ExportCtrl', ['$scope', '$window', 'CredentialService', 'VaultService', '$translate', function ($scope, $window, CredentialService, VaultService, $translate) {
|
.controller('ExportCtrl', ['$scope', '$window', 'CredentialService', 'VaultService', '$translate', function ($scope, $window, CredentialService, VaultService, $translate) {
|
||||||
$scope.available_exporters = [];
|
$scope.available_exporters = [];
|
||||||
$scope.active_vault = VaultService.getActiveVault();
|
$scope.active_vault = VaultService.getActiveVault();
|
||||||
|
$scope.confirm_key = '';
|
||||||
|
|
||||||
$scope.$watch(function () {
|
$scope.$watch(function () {
|
||||||
return $window.PassmanExporter;
|
return $window.PassmanExporter;
|
||||||
|
@ -58,6 +58,13 @@
|
||||||
|
|
||||||
|
|
||||||
$scope.startExport = function () {
|
$scope.startExport = function () {
|
||||||
|
$scope.error = false;
|
||||||
|
if(VaultService.getActiveVault().vaultKey !== $scope.confirm_key){
|
||||||
|
var msg = $translate.instant('invalid.vault.key');
|
||||||
|
$scope.error = msg;
|
||||||
|
_log(msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
_log($translate.instant('export.starting'));
|
_log($translate.instant('export.starting'));
|
||||||
var _credentials = [];
|
var _credentials = [];
|
||||||
VaultService.getVault(VaultService.getActiveVault()).then(function (vault) {
|
VaultService.getVault(VaultService.getActiveVault()).then(function (vault) {
|
||||||
|
@ -67,7 +74,8 @@
|
||||||
for (var i = 0; i < vault.credentials.length; i++) {
|
for (var i = 0; i < vault.credentials.length; i++) {
|
||||||
var _credential = angular.copy(vault.credentials[i]);
|
var _credential = angular.copy(vault.credentials[i]);
|
||||||
if (_credential.hidden === 0) {
|
if (_credential.hidden === 0) {
|
||||||
_credential = CredentialService.decryptCredential(_credential);
|
var key = CredentialService.getSharedKeyFromCredential(_credential);
|
||||||
|
_credential = CredentialService.decryptCredential(_credential, key);
|
||||||
_credentials.push(_credential);
|
_credentials.push(_credential);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
function ($scope, VaultService, $location, $rootScope, TagService, SettingsService) {
|
function ($scope, VaultService, $location, $rootScope, TagService, SettingsService) {
|
||||||
$rootScope.logout = function () {
|
$rootScope.logout = function () {
|
||||||
SettingsService.setSetting('defaultVaultPass', false);
|
SettingsService.setSetting('defaultVaultPass', false);
|
||||||
|
TagService.resetTags();
|
||||||
$rootScope.$broadcast('logout');
|
$rootScope.$broadcast('logout');
|
||||||
$location.path('/');
|
$location.path('/');
|
||||||
};
|
};
|
||||||
|
|
|
@ -254,6 +254,8 @@
|
||||||
CredentialService.updateCredential(c, true).then(function () {
|
CredentialService.updateCredential(c, true).then(function () {
|
||||||
NotificationService.showNotification($translate.instant('credential.unshared'), 4000);
|
NotificationService.showNotification($translate.instant('credential.unshared'), 4000);
|
||||||
$scope.sharing_complete = true;
|
$scope.sharing_complete = true;
|
||||||
|
$scope.storedCredential.shared_key = null;
|
||||||
|
$scope.share_settings.credentialSharedWithUserAndGroup = [];
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -365,6 +367,7 @@
|
||||||
_credential.skip_revision = true;
|
_credential.skip_revision = true;
|
||||||
_credential.shared_key = EncryptService.encryptString(key);
|
_credential.shared_key = EncryptService.encryptString(key);
|
||||||
CredentialService.updateCredential(_credential, true).then(function () {
|
CredentialService.updateCredential(_credential, true).then(function () {
|
||||||
|
$scope.storedCredential.shared_key = _credential.shared_key;
|
||||||
NotificationService.showNotification($translate.instant('credential.shared'), 4000);
|
NotificationService.showNotification($translate.instant('credential.shared'), 4000);
|
||||||
$scope.sharing_complete = true;
|
$scope.sharing_complete = true;
|
||||||
});
|
});
|
||||||
|
|
|
@ -31,7 +31,8 @@
|
||||||
* Controller of the passmanApp
|
* Controller of the passmanApp
|
||||||
*/
|
*/
|
||||||
angular.module('passmanApp')
|
angular.module('passmanApp')
|
||||||
.controller('VaultCtrl', ['$scope', 'VaultService', 'SettingsService', 'CredentialService', '$location', 'ShareService', 'EncryptService', '$translate', '$rootScope', function ($scope, VaultService, SettingsService, CredentialService, $location, ShareService, EncryptService, $translate, $rootScope) {
|
.controller('VaultCtrl', ['$scope', 'VaultService', 'SettingsService', 'CredentialService', '$location', 'ShareService', 'EncryptService', '$translate', '$rootScope', '$interval',
|
||||||
|
function ($scope, VaultService, SettingsService, CredentialService, $location, ShareService, EncryptService, $translate, $rootScope, $interval) {
|
||||||
VaultService.getVaults().then(function (vaults) {
|
VaultService.getVaults().then(function (vaults) {
|
||||||
$scope.vaults = vaults;
|
$scope.vaults = vaults;
|
||||||
if (SettingsService.getSetting('defaultVault') != null) {
|
if (SettingsService.getSetting('defaultVault') != null) {
|
||||||
|
@ -50,6 +51,10 @@
|
||||||
if (SettingsService.getSetting('defaultVaultPass')) {
|
if (SettingsService.getSetting('defaultVaultPass')) {
|
||||||
$location.path('/vault/' + vault.guid);
|
$location.path('/vault/' + vault.guid);
|
||||||
}
|
}
|
||||||
|
$scope.vault_tries[vault.guid] = {
|
||||||
|
tries: 0,
|
||||||
|
timeout: 0
|
||||||
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,12 +79,12 @@
|
||||||
|
|
||||||
var settingsLoaded = function () {
|
var settingsLoaded = function () {
|
||||||
$scope.minimal_value_key_strength = SettingsService.getSetting('vault_key_strength');
|
$scope.minimal_value_key_strength = SettingsService.getSetting('vault_key_strength');
|
||||||
$translate(key_strengths[SettingsService.getSetting('vault_key_strength')]).then(function(translation){
|
$translate(key_strengths[SettingsService.getSetting('vault_key_strength')]).then(function (translation) {
|
||||||
$scope.required_score = {'strength': translation};
|
$scope.required_score = {'strength': translation};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if(!SettingsService.getSetting('settings_loaded')){
|
if (!SettingsService.getSetting('settings_loaded')) {
|
||||||
$rootScope.$on('settings_loaded', function () {
|
$rootScope.$on('settings_loaded', function () {
|
||||||
settingsLoaded();
|
settingsLoaded();
|
||||||
});
|
});
|
||||||
|
@ -107,7 +112,7 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.toggleAutoLogout = function(){
|
$scope.toggleAutoLogout = function () {
|
||||||
$scope.auto_logout_timer = !$scope.auto_logout_timer;
|
$scope.auto_logout_timer = !$scope.auto_logout_timer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -119,6 +124,12 @@
|
||||||
|
|
||||||
$scope.selectVault = function (vault) {
|
$scope.selectVault = function (vault) {
|
||||||
$scope.list_selected_vault = vault;
|
$scope.list_selected_vault = vault;
|
||||||
|
if(!$scope.vault_tries[vault.guid]) {
|
||||||
|
$scope.vault_tries[vault.guid] = {
|
||||||
|
tries: 0,
|
||||||
|
timeout: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
$scope.sharing_keys = {};
|
$scope.sharing_keys = {};
|
||||||
$scope.newVault = function () {
|
$scope.newVault = function () {
|
||||||
|
@ -145,8 +156,8 @@
|
||||||
_vault.vaultKey = angular.copy(vault_key);
|
_vault.vaultKey = angular.copy(vault_key);
|
||||||
delete _vault.credentials;
|
delete _vault.credentials;
|
||||||
var timer = parseInt($scope.logout_timer);
|
var timer = parseInt($scope.logout_timer);
|
||||||
if($scope.auto_logout_timer && timer > 0 ){
|
if ($scope.auto_logout_timer && timer > 0) {
|
||||||
$rootScope.$broadcast('logout_timer_set', timer*60);
|
$rootScope.$broadcast('logout_timer_set', timer * 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
VaultService.setActiveVault(_vault);
|
VaultService.setActiveVault(_vault);
|
||||||
|
@ -158,6 +169,16 @@
|
||||||
$scope.logout_timer = time;
|
$scope.logout_timer = time;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var tickLockTimer = function (guid) {
|
||||||
|
$scope.vault_tries[guid].timeout = $scope.vault_tries[guid].timeout - 1;
|
||||||
|
if($scope.vault_tries[guid].timeout <= 0){
|
||||||
|
$interval.cancel($scope.vault_tries[guid].timer);
|
||||||
|
$scope.vault_tries[guid].timeout = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.vault_tries = {};
|
||||||
|
|
||||||
$scope.vaultDecryptionKey = '';
|
$scope.vaultDecryptionKey = '';
|
||||||
$scope.loginToVault = function (vault, vault_key) {
|
$scope.loginToVault = function (vault, vault_key) {
|
||||||
$scope.error = false;
|
$scope.error = false;
|
||||||
|
@ -174,8 +195,23 @@
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$scope.error = $translate.instant('invalid.vault.key');
|
$scope.error = $translate.instant('invalid.vault.key');
|
||||||
|
|
||||||
|
$scope.vault_tries[vault.guid].tries = $scope.vault_tries[vault.guid].tries + 1;
|
||||||
|
|
||||||
|
if($scope.vault_tries[vault.guid].tries >= 3){
|
||||||
|
var duration = (Math.pow(2, 1 / 7) * Math.pow(15, 4 / 7)) * Math.pow((Math.pow(2, 2 / 7) * Math.pow(15, 1 / 7)), $scope.vault_tries[vault.guid].tries);
|
||||||
|
$scope.vault_tries[vault.guid].timeout = duration;
|
||||||
|
|
||||||
|
if($scope.vault_tries[vault.guid].hasOwnProperty('timer')){
|
||||||
|
$interval.cancel($scope.vault_tries[vault.guid].timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.vault_tries[vault.guid].timer = $interval(function () {
|
||||||
|
tickLockTimer(vault.guid);
|
||||||
|
} ,1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
resetTags: function () {
|
||||||
|
_tags = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -132,3 +132,19 @@ var PassmanImporter = {};
|
||||||
return JSON.parse(string);
|
return JSON.parse(string);
|
||||||
};
|
};
|
||||||
})(window, $, PassmanImporter);
|
})(window, $, PassmanImporter);
|
||||||
|
|
||||||
|
|
||||||
|
String.prototype.replaceAll = function(search, replacement) {
|
||||||
|
var target = this;
|
||||||
|
return target.replace(new RegExp(search, 'g'), replacement);
|
||||||
|
};
|
||||||
|
|
||||||
|
Array.prototype.clean = function(deleteValue) {
|
||||||
|
for (var i = 0; i < this.length; i++) {
|
||||||
|
if (this[i] === deleteValue) {
|
||||||
|
this.splice(i, 1);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
|
@ -29,7 +29,7 @@ var PassmanImporter = PassmanImporter || {};
|
||||||
info: {
|
info: {
|
||||||
name: 'Clipperz.is',
|
name: 'Clipperz.is',
|
||||||
id: 'clippers',
|
id: 'clippers',
|
||||||
description: 'Go to menu -> Export -> Download HTML + JSON. Fields will be imported as custom fields.'
|
exportSteps: ['Go to menu -> Export -> Download HTML + JSON. Fields will be imported as custom fields.']
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ var PassmanImporter = PassmanImporter || {};
|
||||||
info: {
|
info: {
|
||||||
name: 'Dashlane 4 csv',
|
name: 'Dashlane 4 csv',
|
||||||
id: 'dashLaneCsv',
|
id: 'dashLaneCsv',
|
||||||
description: 'Create an csv export. Go to File -> export -> Unsecured archive (readable) in CSV format'
|
exportSteps: ['Create an csv export. Go to File -> export -> Unsecured archive (readable) in CSV format']
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
121
js/importers/importer-enpass.js
Normal file
121
js/importers/importer-enpass.js
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
/**
|
||||||
|
* Nextcloud - passman
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2016, Sander Brand (brantje@gmail.com)
|
||||||
|
* @copyright Copyright (c) 2016, Marcos Zuriaga Miguel (wolfi@wolfi.es)
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Importers should always start with this
|
||||||
|
/** global: PassmanImporter */
|
||||||
|
var PassmanImporter = PassmanImporter || {};
|
||||||
|
(function(window, $, PassmanImporter) {
|
||||||
|
'use strict';
|
||||||
|
// Define the importer
|
||||||
|
PassmanImporter.EnPassTXT = {
|
||||||
|
info: {
|
||||||
|
name: 'EnPass text file',
|
||||||
|
id: 'EnPassTXT',
|
||||||
|
exportSteps: ['Access your Enpass Database. Select "File" > "Export" > "As Text"']
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function parseEnpass(fileData){
|
||||||
|
var lastProperty, matches, loginBlocks, property;
|
||||||
|
loginBlocks = fileData.replaceAll("Title :","<~passman~>\nTitle :").split('<~passman~>\n').clean("");
|
||||||
|
var regex = /(.*) : (.*)/;
|
||||||
|
var results = [];
|
||||||
|
for(var l = 0; l < loginBlocks.length; l++){
|
||||||
|
var loginBlock = loginBlocks[l];
|
||||||
|
var lrow = loginBlock.split('\n');
|
||||||
|
var result = {};
|
||||||
|
for(var r = 0; r < lrow.length; r++){
|
||||||
|
var row = lrow[r];
|
||||||
|
matches = regex.exec(row);
|
||||||
|
if(matches){
|
||||||
|
property = matches[1];
|
||||||
|
result[property] = matches[2];
|
||||||
|
} else {
|
||||||
|
if(lastProperty){
|
||||||
|
result[lastProperty] += "\n" + row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(property) {
|
||||||
|
lastProperty = property;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
results.push(result)
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
PassmanImporter.EnPassTXT.readFile = function (file_data) {
|
||||||
|
var mapper = {
|
||||||
|
'Title': 'label',
|
||||||
|
'Username': 'username',
|
||||||
|
'Password': 'password',
|
||||||
|
'Email': 'email',
|
||||||
|
'Url': 'url',
|
||||||
|
'Note': 'description'
|
||||||
|
};
|
||||||
|
|
||||||
|
var secret_fields = ['cvc', 'pin', 'security answer'];
|
||||||
|
|
||||||
|
/** global: C_Promise */
|
||||||
|
return new C_Promise(function(){
|
||||||
|
var credential_list = [];
|
||||||
|
var credentials = parseEnpass(file_data);
|
||||||
|
for (var i = 0; i < credentials.length; i++) {
|
||||||
|
var enpass_credential = credentials[i];
|
||||||
|
var new_credential = PassmanImporter.newCredential();
|
||||||
|
for(var key in enpass_credential){
|
||||||
|
if(!enpass_credential.hasOwnProperty(key)){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mapper.hasOwnProperty(key)){
|
||||||
|
var prop = mapper[key];
|
||||||
|
new_credential[prop] = enpass_credential[key];
|
||||||
|
} else {
|
||||||
|
if(key !== 'TOTP') {
|
||||||
|
var isSecret = (secret_fields.indexOf(key.toLowerCase()) !== -1) ? 1 : 0;
|
||||||
|
new_credential.custom_fields.push({
|
||||||
|
'label': key,
|
||||||
|
'value': enpass_credential[key],
|
||||||
|
'secret': isSecret
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(enpass_credential.hasOwnProperty('TOTP')){
|
||||||
|
new_credential.otp.secret = enpass_credential['TOTP'];
|
||||||
|
}
|
||||||
|
|
||||||
|
var progress = {
|
||||||
|
percent: i/credentials.length*100,
|
||||||
|
loaded: i,
|
||||||
|
total: credentials.length
|
||||||
|
};
|
||||||
|
|
||||||
|
credential_list.push(new_credential);
|
||||||
|
this.call_progress(progress);
|
||||||
|
}
|
||||||
|
this.call_then(credential_list);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})(window, $, PassmanImporter);
|
|
@ -30,7 +30,7 @@ var PassmanImporter = PassmanImporter || {};
|
||||||
info: {
|
info: {
|
||||||
name: 'KeePass csv',
|
name: 'KeePass csv',
|
||||||
id: 'keepassCsv',
|
id: 'keepassCsv',
|
||||||
description: 'Create an csv export with the following options enabled: http://i.imgur.com/CaeTA4d.png'
|
exportSteps: ['Create an csv export with the following options enabled: http://i.imgur.com/CaeTA4d.png']
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ var PassmanImporter = PassmanImporter || {};
|
||||||
info: {
|
info: {
|
||||||
name: 'LastPass csv',
|
name: 'LastPass csv',
|
||||||
id: 'lastpassCsv',
|
id: 'lastpassCsv',
|
||||||
description: 'Create an csv export. Go to More options -> Advanced -> Export -> Last Pass CSV File'
|
exportSteps: ['Create an csv export. Go to More options -> Advanced -> Export -> Last Pass CSV File']
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
71
js/importers/importer-ocpasswords.js
Normal file
71
js/importers/importer-ocpasswords.js
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
/**
|
||||||
|
* Nextcloud - passman
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2016, Sander Brand (brantje@gmail.com)
|
||||||
|
* @copyright Copyright (c) 2016, Marcos Zuriaga Miguel (wolfi@wolfi.es)
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Importers should always start with this
|
||||||
|
/** global: PassmanImporter */
|
||||||
|
var PassmanImporter = PassmanImporter || {};
|
||||||
|
(function (window, $, PassmanImporter) {
|
||||||
|
'use strict';
|
||||||
|
// Define the importer
|
||||||
|
var steps = [
|
||||||
|
'Backups for the Passwords app need to be enabled on the Admin panel (they are disabled by default).',
|
||||||
|
'On the Passwords App, in the bottom left corner, press Settings',
|
||||||
|
'Press "Download Backup"',
|
||||||
|
'Confirm the export and save the file'
|
||||||
|
];
|
||||||
|
PassmanImporter.passwordsApp = {
|
||||||
|
info: {
|
||||||
|
name: 'Passwords App csv',
|
||||||
|
id: 'passwordsApp',
|
||||||
|
exportSteps: steps
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
PassmanImporter.passwordsApp.readFile = function (file_data) {
|
||||||
|
/** global: C_Promise */
|
||||||
|
var p = new C_Promise(function () {
|
||||||
|
var parsed_csv = PassmanImporter.readCsv(file_data);
|
||||||
|
var credential_list = [];
|
||||||
|
for (var i = 0; i < parsed_csv.length; i++) {
|
||||||
|
var row = parsed_csv[i];
|
||||||
|
var _credential = PassmanImporter.newCredential();
|
||||||
|
_credential.label = row.website + ' - '+ row.username;
|
||||||
|
_credential.username = row.username;
|
||||||
|
_credential.password = row.password;
|
||||||
|
_credential.url = row.fulladdress;
|
||||||
|
_credential.description = row.notes;
|
||||||
|
|
||||||
|
credential_list.push(_credential);
|
||||||
|
|
||||||
|
var progress = {
|
||||||
|
percent: i / parsed_csv.length * 100,
|
||||||
|
loaded: i,
|
||||||
|
total: parsed_csv.length
|
||||||
|
};
|
||||||
|
|
||||||
|
this.call_progress(progress);
|
||||||
|
}
|
||||||
|
this.call_then(credential_list);
|
||||||
|
});
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
})(window, $, PassmanImporter);
|
|
@ -29,7 +29,7 @@ var PassmanImporter = PassmanImporter || {};
|
||||||
info: {
|
info: {
|
||||||
name: 'Passman JSON',
|
name: 'Passman JSON',
|
||||||
id: 'passmanJson',
|
id: 'passmanJson',
|
||||||
description: 'Export the item in passman as passman json, with all fields enabled'
|
exportSteps: ['Export the item in passman as passman json, with all fields enabled']
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ var PassmanImporter = PassmanImporter || {};
|
||||||
info: {
|
info: {
|
||||||
name: 'Passpack csv',
|
name: 'Passpack csv',
|
||||||
id: 'passpackCsv',
|
id: 'passpackCsv',
|
||||||
description: 'Go to Tools -> Export. Select Comma Separated Values, All entries then continue.'
|
exportSteps: ['Go to Tools -> Export. Select Comma Separated Values, All entries then continue.']
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ var PassmanImporter = PassmanImporter || {};
|
||||||
info: {
|
info: {
|
||||||
name: 'Random data',
|
name: 'Random data',
|
||||||
id: 'randomData',
|
id: 'randomData',
|
||||||
description: 'Create\'s 50 random credentials for testing purposes.'
|
exportSteps: ['Create\'s 50 random credentials for testing purposes.']
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
62
js/importers/importer-teampass.js
Normal file
62
js/importers/importer-teampass.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/**
|
||||||
|
* Nextcloud - passman
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2016, Sander Brand (brantje@gmail.com)
|
||||||
|
* @copyright Copyright (c) 2016, Marcos Zuriaga Miguel (wolfi@wolfi.es)
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Importers should always start with this
|
||||||
|
/** global: PassmanImporter */
|
||||||
|
var PassmanImporter = PassmanImporter || {};
|
||||||
|
(function(window, $, PassmanImporter) {
|
||||||
|
'use strict';
|
||||||
|
// Define the importer
|
||||||
|
PassmanImporter.teamPassCsv = {
|
||||||
|
info: {
|
||||||
|
name: 'TeamPass csv',
|
||||||
|
id: 'teamPassCsv',
|
||||||
|
exportSteps: ['Go to Tools -> Export. Select Comma Separated Values, All entries then continue.']
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
PassmanImporter.teamPassCsv.readFile = function (file_data) {
|
||||||
|
/** global: C_Promise */
|
||||||
|
return new C_Promise(function(){
|
||||||
|
var parsed_csv = PassmanImporter.readCsv(file_data, false);
|
||||||
|
var credential_list = [];
|
||||||
|
for (var i = 0; i < parsed_csv.length; i++) {
|
||||||
|
var row = parsed_csv[i];
|
||||||
|
var _credential = PassmanImporter.newCredential();
|
||||||
|
_credential.label = row[1];
|
||||||
|
_credential.description = row[2];
|
||||||
|
_credential.password = row[3];
|
||||||
|
_credential.username = row[4];
|
||||||
|
|
||||||
|
var progress = {
|
||||||
|
percent: i/parsed_csv.length*100,
|
||||||
|
loaded: i,
|
||||||
|
total: parsed_csv.length
|
||||||
|
};
|
||||||
|
|
||||||
|
credential_list.push(_credential);
|
||||||
|
this.call_progress(progress);
|
||||||
|
}
|
||||||
|
this.call_then(credential_list);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})(window, $, PassmanImporter);
|
|
@ -32,7 +32,7 @@ var PassmanImporter = PassmanImporter || {};
|
||||||
info: {
|
info: {
|
||||||
name: 'ZOHO csv',
|
name: 'ZOHO csv',
|
||||||
id: 'zohoCsv',
|
id: 'zohoCsv',
|
||||||
description: 'Create an csv export. Go to Tools -> Export secrets -> Select "General CSV" and click "Export Secrets"'
|
exportSteps: ['Create an csv export. Go to Tools -> Export secrets -> Select "General CSV" and click "Export Secrets"']
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -23,7 +23,11 @@
|
||||||
|
|
||||||
namespace OCA\Passman;
|
namespace OCA\Passman;
|
||||||
|
|
||||||
|
use OCP\IURLGenerator;
|
||||||
|
|
||||||
class Activity implements \OCP\Activity\IExtension {
|
class Activity implements \OCP\Activity\IExtension {
|
||||||
|
const FILTER_PASSMAN = 'passman';
|
||||||
|
const APP_NAME = 'passman';
|
||||||
const TYPE_ITEM_ACTION = 'passman_item_action';
|
const TYPE_ITEM_ACTION = 'passman_item_action';
|
||||||
const TYPE_ITEM_EXPIRED = 'passman_item_expired';
|
const TYPE_ITEM_EXPIRED = 'passman_item_expired';
|
||||||
const TYPE_ITEM_SHARED = 'passman_item_shared';
|
const TYPE_ITEM_SHARED = 'passman_item_shared';
|
||||||
|
@ -49,6 +53,13 @@ class Activity implements \OCP\Activity\IExtension {
|
||||||
const SUBJECT_ITEM_RENAMED_SELF = 'item_renamed_self';
|
const SUBJECT_ITEM_RENAMED_SELF = 'item_renamed_self';
|
||||||
|
|
||||||
|
|
||||||
|
protected $URLGenerator;
|
||||||
|
|
||||||
|
public function __construct( IURLGenerator $URLGenerator) {
|
||||||
|
$this->URLGenerator = $URLGenerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The extension can return an array of additional notification types.
|
* The extension can return an array of additional notification types.
|
||||||
* If no additional types are to be added false is to be returned
|
* If no additional types are to be added false is to be returned
|
||||||
|
@ -57,7 +68,7 @@ class Activity implements \OCP\Activity\IExtension {
|
||||||
* @return array|false
|
* @return array|false
|
||||||
*/
|
*/
|
||||||
public function getNotificationTypes($languageCode) {
|
public function getNotificationTypes($languageCode) {
|
||||||
$l = \OC::$server->getL10N('passman', $languageCode);
|
$l = \OC::$server->getL10N(self::APP_NAME, $languageCode);
|
||||||
return array(
|
return array(
|
||||||
self::TYPE_ITEM_ACTION => $l->t('A Passman item has been created, modified or deleted'),
|
self::TYPE_ITEM_ACTION => $l->t('A Passman item has been created, modified or deleted'),
|
||||||
self::TYPE_ITEM_EXPIRED => $l->t('A Passman item has expired'),
|
self::TYPE_ITEM_EXPIRED => $l->t('A Passman item has expired'),
|
||||||
|
@ -75,7 +86,7 @@ class Activity implements \OCP\Activity\IExtension {
|
||||||
* @return array|false
|
* @return array|false
|
||||||
*/
|
*/
|
||||||
public function filterNotificationTypes($types, $filter) {
|
public function filterNotificationTypes($types, $filter) {
|
||||||
return $types;
|
return $filter === self::FILTER_PASSMAN ? [self::TYPE_ITEM_ACTION, self::TYPE_ITEM_EXPIRED, self::TYPE_ITEM_SHARED, self::TYPE_ITEM_RENAMED] : $types;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -116,8 +127,8 @@ class Activity implements \OCP\Activity\IExtension {
|
||||||
* @return string|false
|
* @return string|false
|
||||||
*/
|
*/
|
||||||
public function translate($app, $text, $params, $stripPath, $highlightParams, $languageCode) {
|
public function translate($app, $text, $params, $stripPath, $highlightParams, $languageCode) {
|
||||||
$l = \OC::$server->getL10NFactory()->get('passman', $languageCode);
|
$l = \OC::$server->getL10NFactory()->get(self::APP_NAME, $languageCode);
|
||||||
if ($app === 'passman') {
|
if ($app === self::APP_NAME) {
|
||||||
switch ($text) {
|
switch ($text) {
|
||||||
case self::SUBJECT_ITEM_CREATED:
|
case self::SUBJECT_ITEM_CREATED:
|
||||||
return $l->t('%1$s has been created by %2$s', $params);
|
return $l->t('%1$s has been created by %2$s', $params);
|
||||||
|
@ -172,7 +183,7 @@ class Activity implements \OCP\Activity\IExtension {
|
||||||
* @return array|false
|
* @return array|false
|
||||||
*/
|
*/
|
||||||
public function getSpecialParameterList($app, $text) {
|
public function getSpecialParameterList($app, $text) {
|
||||||
if ($app === 'passman') {
|
if ($app === self::APP_NAME) {
|
||||||
switch ($text) {
|
switch ($text) {
|
||||||
case self::SUBJECT_ITEM_CREATED:
|
case self::SUBJECT_ITEM_CREATED:
|
||||||
case self::SUBJECT_ITEM_CREATED_SELF:
|
case self::SUBJECT_ITEM_CREATED_SELF:
|
||||||
|
@ -247,14 +258,14 @@ class Activity implements \OCP\Activity\IExtension {
|
||||||
* @return array|false
|
* @return array|false
|
||||||
*/
|
*/
|
||||||
public function getNavigation() {
|
public function getNavigation() {
|
||||||
$l = \OC::$server->getL10N('passman');
|
$l = \OC::$server->getL10N(self::APP_NAME);
|
||||||
return array(
|
return array(
|
||||||
'top' => array(),
|
'top' => array(),
|
||||||
'apps' => array(
|
'apps' => array( self::FILTER_PASSMAN =>
|
||||||
array(
|
array(
|
||||||
'id' => 'passman',
|
'id' => 'passman',
|
||||||
'name' => (string) $l->t('Passwords'),
|
'name' => (string) $l->t('Passwords'),
|
||||||
'url' => '',//FIXME: Currenlty we cannot link directly to links, so this needs to be fixed... this->URLGenerator->linkToRoute('activity.Activities.showList', array('filter' => 'passman')),
|
'url' => $this->URLGenerator->linkToRoute('activity.Activities.showList', ['filter' => self::FILTER_PASSMAN]),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -267,7 +278,7 @@ class Activity implements \OCP\Activity\IExtension {
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function isFilterValid($filterValue) {
|
public function isFilterValid($filterValue) {
|
||||||
return $filterValue === 'passman';
|
return $filterValue === self::FILTER_PASSMAN;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -280,9 +291,13 @@ class Activity implements \OCP\Activity\IExtension {
|
||||||
* @return array|false
|
* @return array|false
|
||||||
*/
|
*/
|
||||||
public function getQueryForFilter($filter) {
|
public function getQueryForFilter($filter) {
|
||||||
if ($filter === 'passman') {
|
if ($filter === self::FILTER_PASSMAN) {
|
||||||
return array('`app` = ?', array('passman'));
|
return [
|
||||||
|
'(`app` = ?)',
|
||||||
|
[self::APP_NAME],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -107,3 +107,14 @@
|
||||||
input[type="checkbox"]{
|
input[type="checkbox"]{
|
||||||
min-height: inherit;
|
min-height: inherit;
|
||||||
}
|
}
|
||||||
|
.alert {
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.alert-danger {
|
||||||
|
color: #a94442;
|
||||||
|
background-color: #f2dede;
|
||||||
|
border-color: #ebccd1;
|
||||||
|
}
|
|
@ -375,6 +375,9 @@
|
||||||
.file_tab {
|
.file_tab {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
.otpText{
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.app_sidebar {
|
.app_sidebar {
|
||||||
h2{
|
h2{
|
||||||
|
|
|
@ -41,3 +41,10 @@
|
||||||
.tab_container.settings{
|
.tab_container.settings{
|
||||||
margin-bottom: 50px;
|
margin-bottom: 50px;
|
||||||
}
|
}
|
||||||
|
.import-steps{
|
||||||
|
padding-left: 16px;
|
||||||
|
li{
|
||||||
|
list-style-type: disc;
|
||||||
|
}
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
|
@ -90,6 +90,9 @@
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.login_opts{
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 768px){
|
@media screen and (max-width: 768px){
|
||||||
|
|
|
@ -82,7 +82,10 @@ script('passman', 'importers/importer-lastpasscsv');
|
||||||
script('passman', 'importers/importer-dashlanecsv');
|
script('passman', 'importers/importer-dashlanecsv');
|
||||||
script('passman', 'importers/importer-zohocsv');
|
script('passman', 'importers/importer-zohocsv');
|
||||||
script('passman', 'importers/importer-passmanjson');
|
script('passman', 'importers/importer-passmanjson');
|
||||||
|
script('passman', 'importers/importer-ocpasswords');
|
||||||
script('passman', 'importers/importer-clipperz');
|
script('passman', 'importers/importer-clipperz');
|
||||||
|
script('passman', 'importers/importer-teampass');
|
||||||
|
script('passman', 'importers/importer-enpass');
|
||||||
script('passman', 'importers/importer-passpackcsv');
|
script('passman', 'importers/importer-passpackcsv');
|
||||||
script('passman', 'importers/importer-randomdata');
|
script('passman', 'importers/importer-randomdata');
|
||||||
script('passman', 'exporters/exporter-main');
|
script('passman', 'exporters/exporter-main');
|
||||||
|
@ -96,7 +99,7 @@ script('passman', 'exporters/exporter-csv');
|
||||||
/*build-css-start*/
|
/*build-css-start*/
|
||||||
style('passman', 'vendor/ng-password-meter/ng-password-meter');
|
style('passman', 'vendor/ng-password-meter/ng-password-meter');
|
||||||
style('passman', 'vendor/bootstrap/bootstrap.min');
|
style('passman', 'vendor/bootstrap/bootstrap.min');
|
||||||
style('passman', 'vendor/bootstrap/bootstrap-theme.min');
|
|
||||||
style('passman', 'vendor/font-awesome/font-awesome.min');
|
style('passman', 'vendor/font-awesome/font-awesome.min');
|
||||||
style('passman', 'vendor/angular-xeditable/xeditable.min');
|
style('passman', 'vendor/angular-xeditable/xeditable.min');
|
||||||
style('passman', 'vendor/ng-tags-input/ng-tags-input.min');
|
style('passman', 'vendor/ng-tags-input/ng-tags-input.min');
|
||||||
|
|
|
@ -9,17 +9,31 @@ $checkVersion = OC::$server->getConfig()->getAppValue('passman', 'check_version'
|
||||||
$AppInstance = new App();
|
$AppInstance = new App();
|
||||||
$localVersion = $AppInstance->getAppInfo("passman")["version"];
|
$localVersion = $AppInstance->getAppInfo("passman")["version"];
|
||||||
if ($checkVersion) {
|
if ($checkVersion) {
|
||||||
// get latest master version
|
// get latest master version
|
||||||
$doc = new DOMDocument();
|
|
||||||
$doc->load('https://raw.githubusercontent.com/nextcloud/passman/master/appinfo/info.xml');
|
|
||||||
$root = $doc->getElementsByTagName("info");
|
|
||||||
$version = false;
|
$version = false;
|
||||||
$githubVersion = $l->t('Unable to get version info');
|
$githubVersion = $l->t('Unable to get version info');
|
||||||
foreach ($root as $element) {
|
|
||||||
$versions = $element->getElementsByTagName("version");
|
$url = 'https://raw.githubusercontent.com/nextcloud/passman/master/appinfo/info.xml';
|
||||||
$version = $versions->item(0)->nodeValue;
|
try {
|
||||||
|
$client = OC::$server->getHTTPClientService()->newClient();
|
||||||
|
$response = $client->get($url);
|
||||||
|
$xml = $response->getBody();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$xml = false;
|
||||||
}
|
}
|
||||||
if ($version) {
|
|
||||||
|
if ($xml) {
|
||||||
|
$loadEntities = libxml_disable_entity_loader(true);
|
||||||
|
$data = @simplexml_load_string($xml);
|
||||||
|
libxml_disable_entity_loader($loadEntities);
|
||||||
|
if ($data !== false) {
|
||||||
|
$version = (string)$data->version;
|
||||||
|
} else {
|
||||||
|
libxml_clear_errors();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($version !== false) {
|
||||||
$githubVersion = $version;
|
$githubVersion = $version;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +50,7 @@ $ciphers = openssl_get_cipher_methods();
|
||||||
} ?>
|
} ?>
|
||||||
Local version: <?php p($localVersion); ?><br/>
|
Local version: <?php p($localVersion); ?><br/>
|
||||||
<?php
|
<?php
|
||||||
if (version_compare($githubVersion, $localVersion) === 1) {
|
if ($checkVersion && version_compare($githubVersion, $localVersion) === 1) {
|
||||||
p($l->t('A newer version of passman is available'));
|
p($l->t('A newer version of passman is available'));
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
|
@ -1,18 +1,24 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<div class="col-xs-2 nopadding">
|
<div class="col-xs-4 nopadding">
|
||||||
{{ 'upload.qr' | translate}}
|
<span class="otpText">{{ 'upload.qr' | translate}}</span>
|
||||||
|
<select ng-model="otpType">
|
||||||
|
<option value="qrcode">Upload QR code</option>
|
||||||
|
<option value="secret">Enter secret</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-6 nopadding">
|
<div class="col-xs-6 nopadding">
|
||||||
<input type="file" qrread on-read="parseQR(qrdata)"
|
<input type="file" qrread on-read="parseQR(qrdata)"
|
||||||
class="input_secret"
|
class="input_secret"
|
||||||
on-read="parseQR(qrdata)"/>
|
on-read="parseQR(qrdata)" ng-show="otpType === 'qrcode'"/>
|
||||||
|
<input type="text" ng-model="storedCredential.otp.secret" ng-show="otpType === 'secret'">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12" ng-if="storedCredential.otp">{{ 'current.qr' | translate}}
|
<div class="col-xs-12" ng-if="storedCredential.otp">
|
||||||
|
<b>{{ 'current.qr' | translate}}</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -22,23 +28,23 @@
|
||||||
<div class="col-sm-4 col-sm-5 col-md-5">
|
<div class="col-sm-4 col-sm-5 col-md-5">
|
||||||
<table ng-show="storedCredential.otp">
|
<table ng-show="storedCredential.otp">
|
||||||
<tr ng-show="storedCredential.otp.type">
|
<tr ng-show="storedCredential.otp.type">
|
||||||
<td>{{ 'type' | translate}}:</td>
|
<td>{{ 'type' | translate}}: </td>
|
||||||
<td>{{storedCredential.otp.type}}</td>
|
<td>{{storedCredential.otp.type}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr ng-show="storedCredential.otp.label">
|
<tr ng-show="storedCredential.otp.label">
|
||||||
<td>{{ 'label' | translate}}:</td>
|
<td>{{ 'label' | translate}}: </td>
|
||||||
<td>{{storedCredential.otp.label}}</td>
|
<td>{{storedCredential.otp.label}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr ng-show="storedCredential.otp.issuer">
|
<tr ng-show="storedCredential.otp.issuer">
|
||||||
<td>{{ 'issuer' | translate}}:</td>
|
<td>{{ 'issuer' | translate}}: </td>
|
||||||
<td>{{storedCredential.otp.issuer}}</td>
|
<td>{{storedCredential.otp.issuer}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr ng-show="storedCredential.otp.secret">
|
<tr ng-show="storedCredential.otp.secret">
|
||||||
<td>{{ 'secret' | translate}}:</td>
|
<td>{{ 'secret' | translate}}: </td>
|
||||||
<td>{{storedCredential.otp.secret}}</td>
|
<td>{{storedCredential.otp.secret}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr ng-show="storedCredential.otp.secret">
|
<tr ng-show="storedCredential.otp.secret">
|
||||||
<td>{{ 'otp' | translate}}:</td>
|
<td>{{ 'otp' | translate}}: </td>
|
||||||
<td><span otp-generator
|
<td><span otp-generator
|
||||||
secret="storedCredential.otp.secret"></span>
|
secret="storedCredential.otp.secret"></span>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -10,13 +10,26 @@
|
||||||
</option>
|
</option>
|
||||||
</select></label>
|
</select></label>
|
||||||
<div><b>{{selectedExporter.description}}</b></div>
|
<div><b>{{selectedExporter.description}}</b></div>
|
||||||
<button class="button" ng-click="startExport()"
|
|
||||||
|
<div ng-show="selectedExporter" class="col-xs-3 nopadding">
|
||||||
|
<label>{{ 'export.confirm.text' | translate }}</label>
|
||||||
|
<input type="password" ng-model="confirm_key">
|
||||||
|
<br />
|
||||||
|
<div class="alert alert-warning" ng-show="error">
|
||||||
|
{{error}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="clearfix">
|
||||||
|
<button class=" button" ng-click="startExport()"
|
||||||
ng-if="selectedExporter">{{ 'export' | translate}}
|
ng-if="selectedExporter">{{ 'export' | translate}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="col-xs-6">
|
<div class="col-xs-6">
|
||||||
<div ng-if="log" class="import_log">
|
<div ng-if="log" class="import_log">
|
||||||
<textarea id="import_log" auto-scroll="log">{{log.join('\n')}}</textarea>
|
<textarea id="import_log"
|
||||||
|
auto-scroll="log">{{log.join('\n')}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,7 +9,12 @@
|
||||||
{{importer.name}}
|
{{importer.name}}
|
||||||
</option>
|
</option>
|
||||||
</select></label>
|
</select></label>
|
||||||
<div><b>{{selectedImporter.description}}</b></div>
|
<div ng-show="selectedImporter">
|
||||||
|
<b>{{ 'import.steps' | translate }}</b>
|
||||||
|
<ul class="import-steps">
|
||||||
|
<li ng-repeat="step in selectedImporter.exportSteps">{{step}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
<input ng-if="selectedImporter" type="file" file-select
|
<input ng-if="selectedImporter" type="file" file-select
|
||||||
success="fileLoaded" error="fileLoadError"
|
success="fileLoaded" error="fileLoadError"
|
||||||
progress="fileSelectProgress"><br/>
|
progress="fileSelectProgress"><br/>
|
||||||
|
|
|
@ -22,6 +22,11 @@
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<small>{{ 'search.result.missing' | translate}}</small>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
|
@ -27,9 +27,9 @@
|
||||||
<div class="tab_container share_credential" ng-show="currentTab">
|
<div class="tab_container share_credential" ng-show="currentTab">
|
||||||
<div ng-include="currentTab.url"></div>
|
<div ng-include="currentTab.url"></div>
|
||||||
|
|
||||||
<button ng-click="applyShare()" ng-disabled="!share_settings.linkSharing.enabled || share_settings.credentialSharedWithUserAndGroup.length > 0">{{ 'share' | translate}}</button>
|
<button ng-click="applyShare()" ng-disabled="share_settings.linkSharing.enabled === false && share_settings.credentialSharedWithUserAndGroup.length === 0">{{ 'share' | translate}}</button>
|
||||||
<button ng-click="cancel()">{{ 'cancel' | translate}}</button>
|
<button ng-click="cancel()">{{ 'cancel' | translate}}</button>
|
||||||
<button class="btn btn-danger" ng-click="unshareCredential(storedCredential)">{{ 'unshare' | translate}}</button>
|
<button class="btn btn-danger" ng-disabled="!storedCredential.shared_key" ng-click="unshareCredential(storedCredential)">{{ 'unshare' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<a><i class="fa fa-home"></i></a>
|
<a><i class="fa fa-home"></i></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="crumb svg last" ng-click="clearState()">
|
<div class="crumb svg last" ng-click="clearState()">
|
||||||
<a>{{active_vault.name}}</a>
|
<span>{{active_vault.name}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -95,7 +95,7 @@
|
||||||
<span ng-if="list_selected_vault.last_access === 0">Never</span>
|
<span ng-if="list_selected_vault.last_access === 0">Never</span>
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="login_opts">
|
||||||
<div>
|
<div>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" ng-checked="default_vault"
|
<input type="checkbox" ng-checked="default_vault"
|
||||||
|
@ -128,10 +128,12 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="button button-geen"
|
|
||||||
ng-click="loginToVault(list_selected_vault, vault_key)">
|
<div class="alert alert-danger" ng-show="vault_tries[list_selected_vault.guid].timeout !== 0" translate="vault.locked" translate-value-tries="{{ vault_tries[list_selected_vault.guid].tries }}" translate-value-time="{{ vault_tries[list_selected_vault.guid].timeout | toHHMMSS }}"></div>
|
||||||
|
<button class="button button-geen"
|
||||||
|
ng-click="loginToVault(list_selected_vault, vault_key)" ng-disabled="vault_tries[list_selected_vault.guid].timeout !== 0">
|
||||||
{{ 'vault.decrypt' | translate}}
|
{{ 'vault.decrypt' | translate}}
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue