This commit is contained in:
brantje 2017-05-07 13:35:02 +02:00
Родитель 8186c35d2e
Коммит 501116bf8d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 5FF1D117F918687F
15 изменённых файлов: 515 добавлений и 22 удалений

Просмотреть файл

@ -97,10 +97,34 @@
"message": "Username",
"description": "Username"
},
"email": {
"message": "E-mail",
"description": "E-mail"
},
"add": {
"message": "Add",
"description": "Add"
},
"credential_created": {
"message": "Credential created",
"description": "Credential created"
},
"url": {
"message": "URL",
"description": "URL"
},
"delete": {
"message": "Delete",
"description": "Delete"
},
"password": {
"message": "Password",
"description": "Password"
},
"password_repeat": {
"message": "Password (repeat)",
"description": "Password"
},
"detected_new_login": {
"message": "Detected new login",
"description": "Detected new login"
@ -251,6 +275,26 @@
"message": "Done",
"description": "Done"
},
"list": {
"message": "List",
"description": "List"
},
"edit": {
"message": "Edit",
"description": "Edit"
},
"server_settings": {
"message": "Server settings",
"description": "Server Settings"
},
"vault_settings": {
"message": "Vault settings",
"description": "Vault settings"
},
"master_password": {
"message": "Set Master password",
"description": "Set a master password"
},
"finish": {
"message": "Finish",
"description": "Finish"
@ -267,10 +311,26 @@
"message": "Prev",
"description": "Previous"
},
"back": {
"message": "Back",
"description": "Back"
},
"copy": {
"message": "Copy",
"description": "Copy"
},
"next": {
"message": "Next",
"description": "Next"
},
"continue": {
"message": "Continue",
"description": "Continue"
},
"donate": {
"message": "Donate",
"description": "Donate"
},
"nextcloud_settings": {
"message": "Nextcloud / ownCloud server settings",
"description": "Nextcloud / ownCloud server settings"
@ -291,6 +351,18 @@
"message": "Vault password",
"description": "Vault password"
},
"label_required": {
"message": "Please fill in a label",
"description": "When comparing passwords"
},
"invalid_host": {
"message": "Invalid server url",
"description": "Invalid server url"
},
"no_password_match": {
"message": "Passwords don't match",
"description": "When comparing passwords"
},
"invalid_vault_password": {
"message": "Invalid vault key!",
"description": "Vault password"
@ -304,7 +376,7 @@
"description": "One time password"
},
"settings": {
"message": "settings",
"message": "Settings",
"description": "Settings"
},
"search": {

Просмотреть файл

@ -19,6 +19,7 @@ body {
overflow: auto;
/*padding-right: 15px;*/ }
#mainPopup .pwcontainer .credential {
position: relative;
border: 1px solid #fff;
margin-bottom: 0.3em;
float: left;
@ -145,18 +146,21 @@ input[type="password"], input[type="text"] {
.nopadding {
padding: 0; }
.pad5 {
padding: 5px; }
.radial-progress {
display: inline-block;
left: 10px;
top: 6px;
left: 5px;
top: 2px;
position: relative;
width: 25px;
height: 25px;
width: 10px;
height: 10px;
background-color: #d6dadc;
border-radius: 50%; }
.radial-progress .circle .mask, .radial-progress .circle .fill {
width: 25px;
height: 25px;
width: 10px;
height: 10px;
position: absolute;
border-radius: 50%;
-webkit-transition: -webkit-transform 0.5s;
@ -165,9 +169,9 @@ input[type="password"], input[type="text"] {
-webkit-backface-visibility: hidden;
backface-visibility: hidden; }
.radial-progress .circle .mask {
clip: rect(0px, 25px, 25px, 12.5px); }
clip: rect(0px, 10px, 10px, 5px); }
.radial-progress .circle .mask .fill {
clip: rect(0px, 12.5px, 25px, 0px);
clip: rect(0px, 5px, 10px, 0px);
background-color: #97a71d; }
.ng-hide {
@ -190,3 +194,23 @@ input[type="password"], input[type="text"] {
cursor: pointer; }
.ignored_sites li .fa:hover {
color: #a94442; }
input {
color: #000; }
.editCredential tr td {
padding: 5px; }
.edit:before {
font: normal normal normal 14px/1 FontAwesome;
content: "\f0da";
height: 100%;
width: 20px;
position: absolute;
right: 0;
top: 0;
bottom: 0;
border-left: 1px solid white;
padding-left: 8px;
padding-top: 5%;
cursor: pointer; }

Просмотреть файл

@ -17,6 +17,7 @@
<script src="/js/lib/API/i18n.js"></script>
<script src="/js/lib/otp.js"></script>
<script src="/js/lib/font-awesome.js"></script>
<script src="/js/lib/passwordgen.js"></script>
<script src="/js/vendor/sjcl/sjcl.js"></script>
<script src="/js/vendor/angular-resource/angular-resource.js"></script>
@ -37,9 +38,11 @@
<script src="/js/ui/popup/controllers/settings.js"></script>
<script src="/js/ui/popup/controllers/password_prompt.js"></script>
<script src="/js/ui/popup/controllers/search.js"></script>
<script src="/js/ui/popup/controllers/setup.js"></script>
<script src="/js/ui/popup/controllers/search.js"></script>
<script src="/js/ui/popup/controllers/edit.js"></script>
<script src="/js/ui/popup/directives/otp.js"></script>
<script src="/js/ui/popup/directives/ngenter.js"></script>
<script src="/js/ui/popup/directives/copyText.js"></script>
<script src="/js/ui/popup/filters/translate.js"></script>

Просмотреть файл

@ -0,0 +1,81 @@
<table class="editCredential table">
<tr>
<td>{{'label' | translate}}</td>
<td>
<input type="text" ng-model="credential.label" required>
<copy-text text="credential.label"></copy-text>
</td>
</tr>
<tr>
<td>{{'username' | translate}}</td>
<td>
<input type="text" ng-model="credential.username"/>
<copy-text text="credential.username"></copy-text>
</td>
</tr>
<tr>
<td>{{'email' | translate}}</td>
<td>
<input type="text" ng-model="credential.email"/>
<copy-text text="credential.email"></copy-text>
</td>
</tr>
<tr>
<td>{{'password' | translate}}</td>
<td>
<input type="text" ng-model="credential.password" ng-if="pwFieldShown">
<input type="password" ng-model="credential.password" ng-if="!pwFieldShown">
<i class="fa fa-refresh pointer" ng-click="generatePassword()" style="left: initial; right: 35px;"></i>
<i class="pointer fa" ng-class="{'fa-eye': !pwFieldShown, 'fa-eye-slash': pwFieldShown}" style="left: initial; right: 70px;" ng-click="togglePwField()"></i>
<copy-text text="credential.password"></copy-text>
</td>
</tr>
<tr>
<td>{{'password_repeat' | translate}}</td>
<td><input type="password" ng-model="credential.password_repeat"></td>
</tr>
<tr>
<td>{{'url' | translate}}</td>
<td>
<input type="text" ng-model="credential.url"/>
<copy-text text="credential.url"></copy-text>
</td>
</tr>
</table>
<table class="editCredential table">
<tr ng-repeat="custom_field in credential.custom_fields" ng-if="custom_field.field_type !== 'file'">
<td>
<input required ng-model="custom_field.label" type="text">
</td>
<td>
<input type="password" ng-model="custom_field.value" ng-if="custom_field.secret">
<input type="text" ng-model="custom_field.value" ng-if="!custom_field.secret">
</td>
</tr>
</table>
<h4>Add custom field</h4>
<div class="col-xs-3 pad5">
<input class="form-control" type="text" ng-model="new_custom_field.label" placeholder="Label">
</div>
<div class="col-xs-3 pad5">
<input class="form-control" type="password" ng-model="new_custom_field.value" ng-if="new_custom_field.field_type === 'password'">
<input class="form-control" type="text" ng-model="new_custom_field.value" ng-if="new_custom_field.field_type === 'text'" placeholder="Value">
</div>
<div class="col-xs-3 pad5">
<select class="form-control" name="type" ng-model="new_custom_field.field_type">
<option value="text">Text</option>
<option value="password">Password</option>
</select>
</div>
<div class="col-xs-3 pad5">
<button class="btn btn-default" ng-click="addCustomField(new_custom_field)">
<i class="fa fa-plus"></i>
</button>
</div>
<div class="clearfix"></div>
<button class="btn btn-success" ng-click="saveCredential()" ng-disabled="saving">{{'save' |
translate}}
</button>
<button class="btn btn-default" ng-click="cancel()">{{'cancel' | translate}}</button>

Просмотреть файл

@ -4,11 +4,15 @@
</div>
<div ng-show="found_credentials.length > 0">{{'credentials_found_for_site' | translate:found_credentials.length.toString() }}</div>
<div class="credential" ng-repeat="credential in found_credentials">
<div class="col-xs-7 nopadding">{{credential.label}}<br/>
<small>{{credential.username}}</small>
<div class="col-xs-10 nopadding">{{credential.label}}<br/>
<small>{{credential.username}}</small><br />
<small ng-if="credential.otp.secret"> {{'one_time_password' | translate}}: <div otp-generator secret="credential.otp.secret"></div></small>
</div>
<div class="col-xs-5 OTP" ng-if="credential.otp.secret">
{{'one_time_password' | translate}}: <div otp-generator secret="credential.otp.secret"></div>
<div class="edit" ng-click="editCredential(credential)">
</div>
<div class="col-xs-2">
</div>
</div>
</div>

Просмотреть файл

@ -62,6 +62,9 @@
<tr>
<td colspan=2><input type="checkbox" id="disableAutoFill" ng-model="settings.disableAutoFill"><small>{{'disable_autofill' | translate}}</small></td>
</tr>
<tr>
<td colspan=2><input type="checkbox" id="disableBrowserAutoFill" ng-model="settings.disable_browser_autofill"><small>{{'disable_browser_autofill' | translate}}</small></td>
</tr>
<tr>
<td colspan=2><input type="checkbox" id="disablePasswordPicker" ng-model="settings.disablePasswordPicker"><small>{{'disable_password_picker' | translate}}</small></td>
</tr>

Просмотреть файл

@ -65,7 +65,7 @@ $j(document).ready(function () {
picker.css('left', left);
picker.css('z-index', maxZ);
picker.css('top', top);
$j('body').append($j(picker));
$j('body').prepend($j(picker));
// picker.css('width', $j(form).width());
$j('.passwordPickerIframe:not(:last)').remove();
}

Просмотреть файл

@ -223,6 +223,17 @@ var background = (function () {
_self.getCredentialsByUrl = getCredentialsByUrl;
function getCredentialByGuid(guid) {
for (var i = 0; i < local_credentials.length; i++) {
var credential = local_credentials[i];
if (credential.guid === guid) {
return credential;
}
}
}
_self.getCredentialByGuid = getCredentialByGuid;
function getCredentialForHTTPAuth(req) {
return getCredentialsByUrl(req.url)[0];
}

Просмотреть файл

@ -108,8 +108,44 @@ window.PAPI = (function () {
callback(credential);
});
},
updateCredential: function (credential, _key, callback) {
credential = this.encryptCredential(credential, _key);
encryptSharedCredential: function (credential, sharedKey, origKey) {
var _credential = credential;
_credential.shared_key = this.encryptString(sharedKey, origKey);
var encrypted_fields = _encryptedFields;
for (var i = 0; i < encrypted_fields.length; i++) {
var field = encrypted_fields[i];
var fieldValue = credential[field];
_credential[field] = this.encryptString(JSON.stringify(fieldValue), sharedKey);
}
return _credential;
},
updateCredential: function (credential, key, callback) {
var origKey = key;
var _credential, _key;
if (!credential.hasOwnProperty('acl') && credential.hasOwnProperty('shared_key')) {
if (credential.shared_key) {
_key = this.decryptString(credential.shared_key);
}
}
if (credential.hasOwnProperty('acl')) {
_key = this.decryptString(credential.acl.shared_key);
}
if (_key) {
_credential = this.encryptSharedCredential(credential, _key, origKey);
} else {
_credential = credential;
}
delete _credential.shared_key;
var regex = /(<([^>]+)>)/ig;
if(_credential.description && _credential.description !== "") {
_credential.description = _credential.description.replace(regex, "");
}
credential = this.encryptCredential(_credential, key);
credential.expire_time = new Date(credential.expire_time).getTime() / 1000;
api_request('/api/v2/credentials/' + credential.guid, 'PATCH', credential, function () {
callback(credential);

Просмотреть файл

@ -53,6 +53,10 @@
templateUrl: 'views/settings.html',
controller: 'SettingsCtrl'
})
.when('/edit/:guid', {
templateUrl: 'views/edit_credential.html',
controller: 'EditCtrl'
})
.when('/locked', {
templateUrl: 'views/password_prompt.html',
controller: 'PasswordPromptCtrl'

Просмотреть файл

@ -0,0 +1,167 @@
/* global API */
/**
* 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/>.
*
*/
(function () {
'use strict';
/**
* @ngdoc function
* @name passmanApp.controller:MainCtrl
* @description
* # MainCtrl
* Controller of the passmanApp
*/
angular.module('passmanExtension')
.controller('EditCtrl', ['$scope', '$routeParams', '$timeout', function ($scope, $routeParams, $timeout) {
API.runtime.sendMessage(API.runtime.id, {
method: "getCredentialByGuid",
args: $routeParams.guid
}).then(function (credential) {
$scope.credential = credential;
$scope.credential.password_repeat = angular.copy(credential.password);
$scope.$apply();
});
var storage = new API.Storage();
function genPwd(settings) {
/* jshint ignore:start */
var password = generatePassword(settings['length'],
settings.useUppercase,
settings.useLowercase,
settings.useDigits,
settings.useSpecialChars,
settings.minimumDigitCount,
settings.avoidAmbiguousCharacters,
settings.requireEveryCharType);
/* jshint ignore:end */
return password;
}
$scope.pw_settings = null;
function getPasswordGenerationSettings(cb) {
var default_settings = {
'length': 12,
'useUppercase': true,
'useLowercase': true,
'useDigits': true,
'useSpecialChars': true,
'minimumDigitCount': 3,
'avoidAmbiguousCharacters': false,
'requireEveryCharType': true
};
storage.get('password_generator_settings').then(function (_settings) {
if (!_settings) {
_settings = default_settings;
}
$scope.pw_settings = _settings;
}).error(function () {
$scope.pw_settings = default_settings;
});
}
getPasswordGenerationSettings();
var custom_field = {
label: '',
value: '',
field_type: 'text',
secret: false
};
$scope.new_custom_field = angular.copy(custom_field);
$scope.addCustomField = function (_field) {
var field = angular.copy(_field);
if (!field.label || !field.value) {
return;
}
$scope.credential.custom_fields.push(field);
$scope.new_custom_field = angular.copy(custom_field);
};
$scope.deleteCustomField = function (field) {
var idx = $scope.credential.custom_fields.indexOf(field);
$scope.credential.custom_fields.splice(idx, 1);
};
$scope.pwFieldShown = false;
$scope.togglePwField = function () {
$scope.pwFieldShown = !$scope.pwFieldShown;
};
var round = 0;
$scope.generatePassword = function () {
var new_password = genPwd($scope.pw_settings);
$scope.credential.password = new_password;
$scope.credential.password_repeat = new_password;
$timeout(function () {
if (round < 10) {
$scope.generatePassword();
round++;
} else {
round = 0;
}
}, 10);
};
$scope.saveCredential = function () {
if (!$scope.credential.label) {
// $mdToast.showSimple(API.i18n.getMessage('label_required'));
return;
}
if ($scope.credential.password !== $scope.credential.password_repeat) {
// $mdToast.showSimple(API.i18n.getMessage('no_password_match'));
return;
}
if ($scope.new_custom_field.label && $scope.new_custom_field.value) {
$scope.credential.custom_fields.push(angular.copy($scope.new_custom_field));
}
delete $scope.credential.password_repeat;
API.runtime.sendMessage(API.runtime.id, {
method: "saveCredential",
args: $scope.credential
}).then(function (credential) {
if (!$scope.credential.credential_id) {
// $mdToast.showSimple(API.i18n.getMessage('credential_created'));
} else {
// $mdToast.showSimple(API.i18n.getMessage('credential_updated'));
}
window.location = '#!/';
});
};
$scope.cancel = function () {
window.location = '#!/';
};
}]);
}());

Просмотреть файл

@ -127,7 +127,9 @@
window.location = '#!/search';
};
$scope.editCredential = function (credential) {
window.location = '#!/edit/' + credential.guid;
};
}]);
}());

Просмотреть файл

@ -0,0 +1,57 @@
/**
* 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/>.
*
*/
(function () {
'use strict';
/**
* @ngdoc directive
* @name passmanApp.directive:passwordGen
* @description
* # passwordGen
*/
angular.module('passmanExtension')
.directive('copyText', ['$compile', '$timeout',
function ($compile, $timeout) {
var strCopy = API.i18n.getMessage('copy');
return {
restrict: 'E',
scope:{
text: '='
},
template: '<i class="pointer fa fa-copy" ng-click="copyText()"></i>',
replace: true,
link: function (scope, el) {
scope.copyText = function () {
var txtToCopy = document.createElement('input');
txtToCopy.value = scope.text;
document.body.appendChild(txtToCopy);
txtToCopy.select();
document.execCommand('copy');
txtToCopy.parentNode.removeChild(txtToCopy);
};
}
};
}
]);
}());

Просмотреть файл

@ -71,7 +71,7 @@
return {
restrict: 'A',
// template: '<span class="otp_generator">{{otp}} <span ng-bind="timeleft"></span></span>',
template: '<span class="otp_generator"><span class="code">{{otp}}</span> <div class="radial-progress"><div class="circle"><div class="mask full"><div class="fill"></div></div><div class="mask half"><div class="fill"></div><div class="fill fix"></div></div></div></div></span>',
template: '<span class="otp_generator"><span class="code">{{otp}}</span> <copy-text text="otp"></copy-text> <div class="radial-progress"><div class="circle"><div class="mask full"><div class="fill"></div></div><div class="mask half"><div class="fill"></div><div class="fill fix"></div></div></div></div></span>',
transclude: false,
scope: {
secret: '='

Просмотреть файл

@ -18,10 +18,12 @@ body {
width: 400px;
font-family: Helvetica, Ubuntu, Arial, sans-serif;
.pwcontainer{
max-height: 195px;
overflow: auto;
/*padding-right: 15px;*/
.credential {
position: relative;
border: 1px solid #fff;
margin-bottom: 0.3em;
float: left;
@ -185,15 +187,18 @@ input[type="password"], input[type="text"] {
padding: 0;
}
.pad5{
padding: 5px;
}
.radial-progress {
$circle-size : 25px;
$circle-size : 10px;
$circle-background : #d6dadc;
$circle-color : #97a71d;
$transition-length : 0.5s;
display: inline-block;
left: 10px;
top: 6px;
left: 5px;
top: 2px;
position: relative;
width: $circle-size;
height: $circle-size;
@ -244,4 +249,28 @@ input[type="password"], input[type="text"] {
color: #a94442;
}
}
}
input{
color: #000;
}
.editCredential{
tr{
td{
padding: 5px;
}
}
}
.edit:before {
font: normal normal normal 14px/1 FontAwesome;
content: "\f0da";
height: 100%;
width: 20px;
position: absolute;
right: 0;
top: 0;
bottom: 0;
border-left: 1px solid white;
padding-left: 8px;
padding-top: 5%;
cursor: pointer;
}