mirror of
https://github.com/bitwarden/browser.git
synced 2024-09-27 04:03:00 +02:00
add support for add/edit/view of login uris
This commit is contained in:
parent
d67b95421c
commit
f952cd5642
2
jslib
2
jslib
@ -1 +1 @@
|
||||
Subproject commit 0d80216921efa53dfc095fd25dde9b370a070da8
|
||||
Subproject commit 848f50afe7b9dc9e193a036c062ef2f0e4d93018
|
@ -455,6 +455,9 @@
|
||||
"uri": {
|
||||
"message": "URI"
|
||||
},
|
||||
"newUri": {
|
||||
"message": "New URI"
|
||||
},
|
||||
"addedItem": {
|
||||
"message": "Added item"
|
||||
},
|
||||
@ -989,5 +992,33 @@
|
||||
},
|
||||
"passwordSafe": {
|
||||
"message": "This password was not found in any known data breaches. It should be safe to use."
|
||||
},
|
||||
"baseDomain": {
|
||||
"message": "Base domain"
|
||||
},
|
||||
"host": {
|
||||
"message": "Host",
|
||||
"description": "A URL's host value. For example, the host of https://sub.domain.com:443 is 'sub.domain.com:443'."
|
||||
},
|
||||
"exact": {
|
||||
"message": "Exact"
|
||||
},
|
||||
"startsWith": {
|
||||
"message": "Starts with"
|
||||
},
|
||||
"regEx": {
|
||||
"message": "Regular expression",
|
||||
"description": "A programming term, also known as 'RegEx'."
|
||||
},
|
||||
"autofillDetection": {
|
||||
"message": "Auto-fill Detection",
|
||||
"description": "URI auto-fill match detection."
|
||||
},
|
||||
"defaultAutofillDetection": {
|
||||
"message": "Default auto-fill detection",
|
||||
"description": "Default URI auto-fill match detection."
|
||||
},
|
||||
"toggleOptions": {
|
||||
"message": "Toggle Options"
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,12 @@ angular
|
||||
folderId: folderId,
|
||||
name: $stateParams.name,
|
||||
type: constantsService.cipherType.login,
|
||||
login: {},
|
||||
login: {
|
||||
uris: [{
|
||||
uri: null,
|
||||
match: null
|
||||
}]
|
||||
},
|
||||
identity: {},
|
||||
card: {},
|
||||
secureNote: {
|
||||
@ -23,13 +28,15 @@ angular
|
||||
};
|
||||
|
||||
if ($stateParams.uri) {
|
||||
$scope.cipher.login.uri = $stateParams.uri;
|
||||
$scope.cipher.login.uris[0].uri = $stateParams.uri;
|
||||
}
|
||||
|
||||
if ($stateParams.cipher) {
|
||||
angular.extend($scope.cipher, $stateParams.cipher);
|
||||
}
|
||||
|
||||
setUriMatchValues();
|
||||
|
||||
$timeout(function () {
|
||||
popupUtilsService.initListSectionItemListeners(document, angular);
|
||||
|
||||
@ -109,6 +116,50 @@ angular
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addUri = function () {
|
||||
if (!$scope.cipher.login) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$scope.cipher.login.uris) {
|
||||
$scope.cipher.login.uris = [];
|
||||
}
|
||||
|
||||
$scope.cipher.login.uris.push({
|
||||
uri: null,
|
||||
match: null,
|
||||
});
|
||||
|
||||
$timeout(function () {
|
||||
popupUtilsService.initListSectionItemListeners(document, angular);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
$scope.removeUri = function (uri) {
|
||||
if (!$scope.cipher.login || !$scope.cipher.login.uris) {
|
||||
return;
|
||||
}
|
||||
|
||||
var index = $scope.cipher.login.uris.indexOf(uri);
|
||||
if (index > -1) {
|
||||
$scope.cipher.login.uris.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.uriMatchChanged = function (uri) {
|
||||
uri.showOptions = uri.showOptions == null ? true : uri.showOptions;
|
||||
if ((!uri.matchValue && uri.matchValue !== 0) || uri.matchValue === '') {
|
||||
uri.match = null;
|
||||
}
|
||||
else {
|
||||
uri.match = parseInt(uri.matchValue);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggleUriOptions = function (u) {
|
||||
u.showOptions = u.showOptions == null && u.match != null ? false : !u.showOptions;
|
||||
};
|
||||
|
||||
$scope.addField = function (type) {
|
||||
if (!$scope.cipher.fields) {
|
||||
$scope.cipher.fields = [];
|
||||
@ -142,4 +193,14 @@ angular
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function setUriMatchValues() {
|
||||
if ($scope.cipher.login && $scope.cipher.login.uris) {
|
||||
for (var i = 0; i < $scope.cipher.login.uris.length; i++) {
|
||||
$scope.cipher.login.uris[i].matchValue =
|
||||
$scope.cipher.login.uris[i].match || $scope.cipher.login.uris[i].match === 0 ?
|
||||
$scope.cipher.login.uris[i].match.toString() : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -24,12 +24,14 @@ angular
|
||||
|
||||
if ($stateParams.cipher) {
|
||||
angular.extend($scope.cipher, $stateParams.cipher);
|
||||
setUriMatchValues();
|
||||
}
|
||||
else {
|
||||
cipherService.get(cipherId).then(function (cipher) {
|
||||
return cipher.decrypt();
|
||||
}).then(function (model) {
|
||||
$scope.cipher = model;
|
||||
setUriMatchValues();
|
||||
});
|
||||
}
|
||||
|
||||
@ -127,6 +129,50 @@ angular
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addUri = function () {
|
||||
if (!$scope.cipher.login) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$scope.cipher.login.uris) {
|
||||
$scope.cipher.login.uris = [];
|
||||
}
|
||||
|
||||
$scope.cipher.login.uris.push({
|
||||
uri: null,
|
||||
match: null,
|
||||
});
|
||||
|
||||
$timeout(function () {
|
||||
popupUtilsService.initListSectionItemListeners(document, angular);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
$scope.removeUri = function (uri) {
|
||||
if (!$scope.cipher.login || !$scope.cipher.login.uris) {
|
||||
return;
|
||||
}
|
||||
|
||||
var index = $scope.cipher.login.uris.indexOf(uri);
|
||||
if (index > -1) {
|
||||
$scope.cipher.login.uris.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.uriMatchChanged = function (uri) {
|
||||
uri.showOptions = uri.showOptions == null ? true : uri.showOptions;
|
||||
if ((!uri.matchValue && uri.matchValue !== 0) || uri.matchValue === '') {
|
||||
uri.match = null;
|
||||
}
|
||||
else {
|
||||
uri.match = parseInt(uri.matchValue);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggleUriOptions = function (u) {
|
||||
u.showOptions = u.showOptions == null && u.match != null ? false : !u.showOptions;
|
||||
};
|
||||
|
||||
$scope.addField = function (type) {
|
||||
if (!$scope.cipher.fields) {
|
||||
$scope.cipher.fields = [];
|
||||
@ -182,4 +228,14 @@ angular
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setUriMatchValues() {
|
||||
if ($scope.cipher.login && $scope.cipher.login.uris) {
|
||||
for (var i = 0; i < $scope.cipher.login.uris.length; i++) {
|
||||
$scope.cipher.login.uris[i].matchValue =
|
||||
$scope.cipher.login.uris[i].match || $scope.cipher.login.uris[i].match === 0 ?
|
||||
$scope.cipher.login.uris[i].match.toString() : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -28,20 +28,6 @@ angular
|
||||
if (model.login.password) {
|
||||
$scope.cipher.maskedPassword = $scope.maskValue(model.login.password);
|
||||
}
|
||||
|
||||
if (model.login.uri) {
|
||||
$scope.cipher.showLaunch = model.login.uri.startsWith('http://') || model.login.uri.startsWith('https://');
|
||||
var domain = platformUtilsService.getDomain(model.login.uri);
|
||||
if (domain) {
|
||||
$scope.cipher.login.website = domain;
|
||||
}
|
||||
else {
|
||||
$scope.cipher.login.website = model.login.uri;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$scope.cipher.showLaunch = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (model.login && model.login.totp && (cipherObj.organizationUseTotp || tokenService.getPremium())) {
|
||||
@ -90,11 +76,13 @@ angular
|
||||
}
|
||||
};
|
||||
|
||||
$scope.launchWebsite = function (cipher) {
|
||||
if (cipher.showLaunch) {
|
||||
$analytics.eventTrack('Launched Website');
|
||||
BrowserApi.createNewTab(cipher.login.uri);
|
||||
$scope.launch = function (uri) {
|
||||
if (!uri.canLaunch) {
|
||||
return;
|
||||
}
|
||||
|
||||
$analytics.eventTrack('Launched Login URI');
|
||||
BrowserApi.createNewTab(uri.uri);
|
||||
};
|
||||
|
||||
$scope.clipboardError = function (e, password) {
|
||||
|
@ -30,10 +30,6 @@
|
||||
<input id="name" type="text" name="Name" ng-model="cipher.name">
|
||||
</div>
|
||||
<div ng-if="cipher.type === constants.cipherType.login">
|
||||
<div class="list-section-item">
|
||||
<label for="loginUri" class="item-label">{{i18n.uri}}</label>
|
||||
<input id="loginUri" type="text" name="Login.Uri" ng-model="cipher.login.uri">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="loginUsername" class="item-label">{{i18n.username}}</label>
|
||||
<input id="loginUsername" type="text" name="Login.Username" ng-model="cipher.login.username">
|
||||
@ -50,12 +46,15 @@
|
||||
<a class="btn-list" href="" title="{{i18n.togglePassword}}" ng-click="togglePassword()">
|
||||
<i class="fa fa-lg" ng-class="[{'fa-eye': !showPassword}, {'fa-eye-slash': showPassword}]"></i>
|
||||
</a>
|
||||
<a class="btn-list" href="" title="{{i18n.generatePassword}}" ng-click="generatePassword()">
|
||||
<i class="fa fa-lg fa-refresh"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a class="list-section-item" href="" ng-click="generatePassword()">
|
||||
{{i18n.generatePassword}}
|
||||
<i class="fa fa-chevron-right"></i>
|
||||
</a>
|
||||
<div class="list-section-item">
|
||||
<label for="loginTotp" class="item-label">{{i18n.authenticatorKeyTotp}}</label>
|
||||
<input id="loginTotp" type="text" name="Login.Totp" ng-model="cipher.login.totp">
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="cipher.type === constants.cipherType.card">
|
||||
<div class="list-section-item">
|
||||
@ -202,12 +201,44 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section" ng-if="cipher.type === constants.cipherType.login">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-table"
|
||||
ng-if="cipher.login.uris && cipher.login.uris.length" ng-repeat="u in cipher.login.uris">
|
||||
<a href="#" stop-click ng-click="removeUri(u)" class="action-button text-danger">
|
||||
<i class="fa fa-minus-circle fa-lg"></i>
|
||||
</a>
|
||||
<div class="action-button-content">
|
||||
<label for="loginUri{{$index}}" class="item-label">{{i18n.uri}} {{$index + 1}}</label>
|
||||
<input id="loginUri{{$index}}" type="text" name="Login.Uris[{{$index}}].Uri"
|
||||
ng-model="u.uri" placeholder="{{i18n.ex}} https://google.com">
|
||||
<label for="loginUriMatch{{$index}}" class="sr-only">
|
||||
{{i18n.autofillDetection}} {{$index + 1}}
|
||||
</label>
|
||||
<select id="loginUriMatch{{$index}}" name="Login.Uris[{{$index}}].Match"
|
||||
ng-hide="u.showOptions === false || (u.showOptions == null && u.match == null)"
|
||||
ng-model="u.matchValue" ng-change="uriMatchChanged(u)">
|
||||
<option value="">{{i18n.defaultAutofillDetection}}</option>
|
||||
<option value="0">{{i18n.baseDomain}}</option>
|
||||
<option value="1">{{i18n.host}}</option>
|
||||
<option value="2">{{i18n.startsWith}}</option>
|
||||
<option value="4">{{i18n.regEx}}</option>
|
||||
<option value="3">{{i18n.exact}}</option>
|
||||
<option value="5">{{i18n.never}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<a href="#" stop-click ng-click="toggleUriOptions(u)" class="action-button"
|
||||
title="{{i18n.toggleOptions}}">
|
||||
<i class="fa fa-cog fa-lg"></i>
|
||||
</a>
|
||||
</div>
|
||||
<a class="list-section-item text-primary" href="#" stop-click ng-click="addUri()">
|
||||
<i class="fa fa-plus-circle fa-fw fa-lg"></i> {{i18n.newUri}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item" ng-if="cipher.type === constants.cipherType.login">
|
||||
<label for="loginTotp" class="item-label">{{i18n.authenticatorKeyTotp}}</label>
|
||||
<input id="loginTotp" type="text" name="Login.Totp" ng-model="cipher.login.totp">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="folder" class="item-label">{{i18n.folder}}</label>
|
||||
<select id="folder" name="FolderId" ng-model="cipher.folderId">
|
||||
@ -241,7 +272,7 @@
|
||||
ng-if="cipher.fields && cipher.fields.length" ng-repeat="field in cipher.fields"
|
||||
ng-class="{'list-section-item-checkbox' : field.type === constants.fieldType.boolean}">
|
||||
<a href="#" stop-click ng-click="removeField(field)" class="action-button text-danger">
|
||||
<i class="fa fa-close fa-lg"></i>
|
||||
<i class="fa fa-minus-circle fa-lg"></i>
|
||||
</a>
|
||||
<div class="action-button-content">
|
||||
<input id="field_name{{$index}}" type="text" name="Field.Name{{$index}}"
|
||||
|
@ -23,10 +23,6 @@
|
||||
</div>
|
||||
|
||||
<div ng-if="cipher.type === constants.cipherType.login">
|
||||
<div class="list-section-item">
|
||||
<label for="loginUri" class="item-label">{{i18n.uri}}</label>
|
||||
<input id="loginUri" type="text" name="Login.Uri" ng-model="cipher.login.uri">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="loginUsername" class="item-label">{{i18n.username}}</label>
|
||||
<input id="loginUsername" type="text" name="Login.Username" ng-model="cipher.login.username">
|
||||
@ -43,12 +39,15 @@
|
||||
<a class="btn-list" href="" title="{{i18n.togglePassword}}" ng-click="togglePassword()">
|
||||
<i class="fa fa-lg" ng-class="[{'fa-eye': !showPassword}, {'fa-eye-slash': showPassword}]"></i>
|
||||
</a>
|
||||
<a class="btn-list" href="" title="{{i18n.generatePassword}}" ng-click="generatePassword()">
|
||||
<i class="fa fa-lg fa-refresh"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a class="list-section-item" href="" ng-click="generatePassword()">
|
||||
{{i18n.generatePassword}}
|
||||
<i class="fa fa-chevron-right"></i>
|
||||
</a>
|
||||
<div class="list-section-item">
|
||||
<label for="loginTotp" class="item-label">{{i18n.authenticatorKeyTotp}}</label>
|
||||
<input id="loginTotp" type="text" name="Login.Totp" ng-model="cipher.login.totp">
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="cipher.type === constants.cipherType.card">
|
||||
<div class="list-section-item">
|
||||
@ -195,12 +194,44 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section" ng-if="cipher.type === constants.cipherType.login">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-table"
|
||||
ng-if="cipher.login.uris && cipher.login.uris.length" ng-repeat="u in cipher.login.uris">
|
||||
<a href="#" stop-click ng-click="removeUri(u)" class="action-button text-danger">
|
||||
<i class="fa fa-minus-circle fa-lg"></i>
|
||||
</a>
|
||||
<div class="action-button-content">
|
||||
<label for="loginUri{{$index}}" class="item-label">{{i18n.uri}} {{$index + 1}}</label>
|
||||
<input id="loginUri{{$index}}" type="text" name="Login.Uris[{{$index}}].Uri"
|
||||
ng-model="u.uri" placeholder="{{i18n.ex}} https://google.com">
|
||||
<label for="loginUriMatch{{$index}}" class="sr-only">
|
||||
{{i18n.autofillDetection}} {{$index + 1}}
|
||||
</label>
|
||||
<select id="loginUriMatch{{$index}}" name="Login.Uris[{{$index}}].Match"
|
||||
ng-hide="u.showOptions === false || (u.showOptions == null && u.match == null)"
|
||||
ng-model="u.matchValue" ng-change="uriMatchChanged(u)">
|
||||
<option value="">{{i18n.defaultAutofillDetection}}</option>
|
||||
<option value="0">{{i18n.baseDomain}}</option>
|
||||
<option value="1">{{i18n.host}}</option>
|
||||
<option value="2">{{i18n.startsWith}}</option>
|
||||
<option value="4">{{i18n.regEx}}</option>
|
||||
<option value="3">{{i18n.exact}}</option>
|
||||
<option value="5">{{i18n.never}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<a href="#" stop-click ng-click="toggleUriOptions(u)" class="action-button"
|
||||
title="{{i18n.toggleOptions}}">
|
||||
<i class="fa fa-cog fa-lg"></i>
|
||||
</a>
|
||||
</div>
|
||||
<a class="list-section-item text-primary" href="#" stop-click ng-click="addUri()">
|
||||
<i class="fa fa-plus-circle fa-fw fa-lg"></i> {{i18n.newUri}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item" ng-if="cipher.type === constants.cipherType.login">
|
||||
<label for="loginTotp" class="item-label">{{i18n.authenticatorKeyTotp}}</label>
|
||||
<input id="loginTotp" type="text" name="Login.Totp" ng-model="cipher.login.totp">
|
||||
</div>
|
||||
<div class="list-section-item">
|
||||
<label for="folder" class="item-label">{{i18n.folder}}</label>
|
||||
<select id="folder" name="FolderId" ng-model="cipher.folderId">
|
||||
@ -238,7 +269,7 @@
|
||||
ng-if="cipher.fields && cipher.fields.length" ng-repeat="field in cipher.fields"
|
||||
ng-class="{'list-section-item-checkbox' : field.type === constants.fieldType.boolean}">
|
||||
<a href="#" stop-click ng-click="removeField(field)" class="action-button text-danger">
|
||||
<i class="fa fa-close fa-lg"></i>
|
||||
<i class="fa fa-minus-circle fa-lg"></i>
|
||||
</a>
|
||||
<div class="action-button-content">
|
||||
<input id="field_name{{$index}}" type="text" name="Field.Name{{$index}}"
|
||||
|
@ -20,22 +20,6 @@
|
||||
{{cipher.name}}
|
||||
</div>
|
||||
<div ng-if="cipher.type === constants.cipherType.login">
|
||||
<div class="list-section-item wrap" ng-if="cipher.login.uri" title="{{cipher.login.uri}}">
|
||||
<div class="action-buttons">
|
||||
<a class="btn-list" href="" title="{{i18n.launchWebsite}}" ng-click="launchWebsite(cipher)"
|
||||
ng-show="cipher.showLaunch">
|
||||
<i class="fa fa-lg fa-share-square-o"></i>
|
||||
</a>
|
||||
<a class="btn-list" href="" title="{{i18n.copyUri}}"
|
||||
ngclipboard ngclipboard-error="clipboardError(e)"
|
||||
ngclipboard-success="clipboardSuccess(e, i18n.uri, 'URI')"
|
||||
data-clipboard-text="{{cipher.login.uri}}">
|
||||
<i class="fa fa-lg fa-clipboard"></i>
|
||||
</a>
|
||||
</div>
|
||||
<span class="item-label">{{i18n.website}}</span>
|
||||
{{cipher.login.website}}
|
||||
</div>
|
||||
<div class="list-section-item wrap" ng-if="cipher.login.username">
|
||||
<div class="action-buttons">
|
||||
<a class="btn-list" href="" title="{{i18n.copyUsername}}"
|
||||
@ -183,6 +167,28 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section"
|
||||
ng-if="cipher.type === constants.cipherType.login && cipher.login.uris && cipher.login.uris.length">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item wrap" title="{{u.uri}}" ng-repeat="u in cipher.login.uris">
|
||||
<div class="action-buttons">
|
||||
<a class="btn-list" href="" title="{{i18n.launchWebsite}}" ng-click="launch(u)"
|
||||
ng-show="u.canLaunch">
|
||||
<i class="fa fa-lg fa-share-square-o"></i>
|
||||
</a>
|
||||
<a class="btn-list" href="" title="{{i18n.copyUri}}"
|
||||
ngclipboard ngclipboard-error="clipboardError(e)"
|
||||
ngclipboard-success="clipboardSuccess(e, i18n.uri, 'URI')"
|
||||
data-clipboard-text="{{u.uri}}">
|
||||
<i class="fa fa-lg fa-clipboard"></i>
|
||||
</a>
|
||||
</div>
|
||||
<span class="item-label" ng-if="u.isWebsite">{{i18n.website}}</span>
|
||||
<span class="item-label" ng-if="!u.isWebsite">{{i18n.uri}}</span>
|
||||
{{u.domainOrUri}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section" ng-if="cipher.notes">
|
||||
<div class="list-section-header">
|
||||
{{i18n.notes}}
|
||||
|
@ -329,7 +329,7 @@
|
||||
}
|
||||
|
||||
.action-button {
|
||||
padding: 8px 10px 8px 5px;
|
||||
padding: 8px 8px 8px 4px;
|
||||
display: table-cell;
|
||||
width: 20px;
|
||||
vertical-align: middle;
|
||||
@ -342,6 +342,14 @@
|
||||
.action-button-content {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
|
||||
input + label.sr-only + select {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-button-content + .action-button {
|
||||
padding: 8px 0 8px 14px;
|
||||
}
|
||||
|
||||
.field-type {
|
||||
|
Loading…
Reference in New Issue
Block a user