mirror of https://github.com/keeweb/keeweb.git
enter otp manually
This commit is contained in:
parent
33f9df3fe3
commit
98ebff0534
|
@ -24,11 +24,12 @@ var OtpQrReader = {
|
|||
Locale.detSetupOtpAlertBodyWith.replace('{}',
|
||||
'<code>' + FeatureDetector.actionShortcutSymbol() + 'V</code>');
|
||||
OtpQrReader.startListenClipoard();
|
||||
var buttons = [Alerts.buttons.cancel];
|
||||
var buttons = [{result: 'manually', title: Locale.detSetupOtpManualButton, silent: true},
|
||||
Alerts.buttons.cancel];
|
||||
if (FeatureDetector.isMobile()) {
|
||||
buttons.unshift({result: 'select', title: Locale.detSetupOtoScanButton});
|
||||
buttons.unshift({result: 'select', title: Locale.detSetupOtpScanButton});
|
||||
}
|
||||
var lastLine = FeatureDetector.isMobile() ? Locale.detSetupOtpAlertBody3Mobile :
|
||||
var line3 = FeatureDetector.isMobile() ? Locale.detSetupOtpAlertBody3Mobile :
|
||||
Locale.detSetupOtpAlertBody3.replace('{}', pasteKey || '');
|
||||
OtpQrReader.alert = Alerts.alert({
|
||||
icon: 'qrcode',
|
||||
|
@ -36,7 +37,8 @@ var OtpQrReader = {
|
|||
body: [Locale.detSetupOtpAlertBody,
|
||||
Locale.detSetupOtpAlertBody1,
|
||||
Locale.detSetupOtpAlertBody2.replace('{}', screenshotKey || ''),
|
||||
lastLine
|
||||
line3,
|
||||
Locale.detSetupOtpAlertBody4
|
||||
].join('<br/>'),
|
||||
esc: '',
|
||||
click: '',
|
||||
|
@ -47,6 +49,8 @@ var OtpQrReader = {
|
|||
OtpQrReader.stopListenClipboard();
|
||||
if (res === 'select') {
|
||||
OtpQrReader.selectFile();
|
||||
} else if (res === 'manually') {
|
||||
OtpQrReader.enterManually();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -150,6 +154,10 @@ var OtpQrReader = {
|
|||
image.src = imageData;
|
||||
},
|
||||
|
||||
enterManually: function() {
|
||||
OtpQrReader.trigger('enter-manually');
|
||||
},
|
||||
|
||||
removeAlert: function() {
|
||||
if (OtpQrReader.alert) {
|
||||
OtpQrReader.alert.closeImmediate();
|
||||
|
|
|
@ -377,7 +377,9 @@ var EntryModel = Backbone.Model.extend({
|
|||
if (otpUrl.isProtected) {
|
||||
otpUrl = otpUrl.getText();
|
||||
}
|
||||
if (otpUrl.toLowerCase().lastIndexOf('otpauth:', 0) !== 0) {
|
||||
if (Otp.isSecret(otpUrl)) {
|
||||
otpUrl = Otp.makeUrl(otpUrl);
|
||||
} else if (otpUrl.toLowerCase().lastIndexOf('otpauth:', 0) !== 0) {
|
||||
// KeeOTP plugin format
|
||||
var args = {};
|
||||
otpUrl.split('&').forEach(function(part) {
|
||||
|
@ -385,34 +387,33 @@ var EntryModel = Backbone.Model.extend({
|
|||
args[parts[0]] = decodeURIComponent(parts[1]).replace(/=/g, '');
|
||||
});
|
||||
if (args.key) {
|
||||
otpUrl = 'otpauth://totp/null?secret=' + args.key +
|
||||
(args.step ? '&period=' + args.step : '') +
|
||||
(args.size ? '&digits=' + args.size : '');
|
||||
otpUrl = Otp.makeUrl(args.key, args.step, args.size);
|
||||
}
|
||||
}
|
||||
} else if (this.entry.fields['TOTP Seed']) {
|
||||
// TrayTOTP plugin format
|
||||
var key = this.entry.fields['TOTP Seed'];
|
||||
if (key.isProtected) {
|
||||
key = key.getText();
|
||||
var secret = this.entry.fields['TOTP Seed'];
|
||||
if (secret.isProtected) {
|
||||
secret = secret.getText();
|
||||
}
|
||||
if (key) {
|
||||
otpUrl = 'otpauth://totp/null?secret=' + key;
|
||||
}
|
||||
var settings = this.entry.fields['TOTP Settings'];
|
||||
if (settings && settings.isProtected) {
|
||||
settings = settings.getText();
|
||||
}
|
||||
if (settings) {
|
||||
settings = settings.split(';');
|
||||
if (settings.length > 0 && settings[0] > 0) {
|
||||
otpUrl += '&period=' + settings[0];
|
||||
if (secret) {
|
||||
var settings = this.entry.fields['TOTP Settings'];
|
||||
if (settings && settings.isProtected) {
|
||||
settings = settings.getText();
|
||||
}
|
||||
if (settings.length > 1 && settings[1] > 0) {
|
||||
otpUrl += '&digits=' + settings[1];
|
||||
var period, digits;
|
||||
if (settings) {
|
||||
settings = settings.split(';');
|
||||
if (settings.length > 0 && settings[0] > 0) {
|
||||
period = settings[0];
|
||||
}
|
||||
if (settings.length > 1 && settings[1] > 0) {
|
||||
digits = settings[1];
|
||||
}
|
||||
}
|
||||
otpUrl = Otp.makeUrl(secret, period, digits);
|
||||
this.fields.otp = kdbxweb.ProtectedValue.fromString(otpUrl);
|
||||
}
|
||||
this.fields.otp = kdbxweb.ProtectedValue.fromString(otpUrl);
|
||||
}
|
||||
if (otpUrl) {
|
||||
if (this.otpGenerator && this.otpGenerator.url === otpUrl) {
|
||||
|
@ -434,7 +435,7 @@ var EntryModel = Backbone.Model.extend({
|
|||
},
|
||||
|
||||
setOtpUrl: function(url) {
|
||||
this.setField('otp', kdbxweb.ProtectedValue.fromString(url));
|
||||
this.setField('otp', url ? kdbxweb.ProtectedValue.fromString(url) : undefined);
|
||||
delete this.entry.fields['TOTP Seed'];
|
||||
delete this.entry.fields['TOTP Settings'];
|
||||
}
|
||||
|
|
|
@ -207,7 +207,9 @@ var Locale = {
|
|||
detSetupOtpAlertBody2: '2. make a screenshot of the QR code {}',
|
||||
detSetupOtpAlertBody3: '3. paste it here {}',
|
||||
detSetupOtpAlertBody3Mobile: '3. select it or scan with your camera using Select/Scan button below',
|
||||
detSetupOtoScanButton: 'Select/Scan',
|
||||
detSetupOtpAlertBody4: 'If you can\'t scan code, click Enter code manually',
|
||||
detSetupOtpManualButton: 'Enter code manually',
|
||||
detSetupOtpScanButton: 'Select/Scan',
|
||||
detSetupOtpAlertBodyWith: 'with {}',
|
||||
detOtpImageError: 'Error reading image',
|
||||
detOtpImageErrorBody: 'Sorry, we could not read the image format, please contact the app authors with error details.',
|
||||
|
|
|
@ -37,6 +37,9 @@ var Otp = function(url, params) {
|
|||
this.period = params.period ? +params.period : 30;
|
||||
|
||||
this.key = Otp.fromBase32(this.secret);
|
||||
if (!this.key) {
|
||||
throw 'Bad key: ' + this.key;
|
||||
}
|
||||
};
|
||||
|
||||
Otp.prototype.next = function(callback) {
|
||||
|
@ -101,7 +104,7 @@ Otp.fromBase32 = function(str) {
|
|||
for (i = 0; i < str.length; i++) {
|
||||
var ix = alphabet.indexOf(str[i].toLowerCase());
|
||||
if (ix < 0) {
|
||||
throw 'Bad base32: ' + str;
|
||||
return null;
|
||||
}
|
||||
bin += Otp.leftPad(ix.toString(2), 5);
|
||||
}
|
||||
|
@ -142,4 +145,12 @@ Otp.parseUrl = function(url) {
|
|||
return new Otp(url, params);
|
||||
};
|
||||
|
||||
Otp.isSecret = function(str) {
|
||||
return !!Otp.fromBase32(str);
|
||||
};
|
||||
|
||||
Otp.makeUrl = function(secret, period, digits) {
|
||||
return 'otpauth://totp/default?secret=' + secret + (period ? '&period=' + period : '') + (digits ? '&digits=' + digits : '');
|
||||
};
|
||||
|
||||
module.exports = Otp;
|
||||
|
|
|
@ -65,6 +65,7 @@ var DetailsView = Backbone.View.extend({
|
|||
this.listenTo(Backbone, 'copy-user', this.copyUserName);
|
||||
this.listenTo(Backbone, 'copy-url', this.copyUrl);
|
||||
this.listenTo(OtpQrReqder, 'qr-read', this.otpCodeRead);
|
||||
this.listenTo(OtpQrReqder, 'enter-manually', this.otpEnterManually);
|
||||
KeyHandler.onKey(Keys.DOM_VK_C, this.copyPassword, this, KeyHandler.SHORTCUT_ACTION, false, true);
|
||||
KeyHandler.onKey(Keys.DOM_VK_B, this.copyUserName, this, KeyHandler.SHORTCUT_ACTION, false, true);
|
||||
KeyHandler.onKey(Keys.DOM_VK_U, this.copyUrl, this, KeyHandler.SHORTCUT_ACTION, false, true);
|
||||
|
@ -488,7 +489,11 @@ var DetailsView = Backbone.View.extend({
|
|||
if (oldValue && oldValue.isProtected) {
|
||||
oldValue = oldValue.getText();
|
||||
}
|
||||
if (value && value.isProtected) {
|
||||
value = value.getText();
|
||||
}
|
||||
if (oldValue === value) {
|
||||
this.render();
|
||||
return false;
|
||||
}
|
||||
this.model.setOtpUrl(value);
|
||||
|
@ -712,6 +717,26 @@ var DetailsView = Backbone.View.extend({
|
|||
otpCodeRead: function(otp) {
|
||||
this.model.setOtp(otp);
|
||||
this.entryUpdated();
|
||||
},
|
||||
|
||||
otpEnterManually: function() {
|
||||
if (this.model.fields.otp) {
|
||||
var otpField = this.fieldViews.find(function(f) { return f.model.name === '$otp'; });
|
||||
if (otpField) {
|
||||
otpField.edit();
|
||||
}
|
||||
} else {
|
||||
this.moreView.remove();
|
||||
this.moreView = null;
|
||||
var fieldView = new FieldViewCustom({ model: {
|
||||
name: '$otp', title: 'otp', newField: 'otp',
|
||||
value: kdbxweb.ProtectedValue.fromString('')
|
||||
}});
|
||||
fieldView.on('change', this.fieldChanged.bind(this));
|
||||
fieldView.setElement(this.$el.find('.details__body-fields')).render();
|
||||
fieldView.edit();
|
||||
this.fieldViews.push(fieldView);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -70,7 +70,9 @@ var FieldViewOtp = FieldViewText.extend({
|
|||
this.otpValue = pass || '';
|
||||
this.otpTimeLeft = timeLeft || 0;
|
||||
this.otpValidUntil = Date.now() + timeLeft;
|
||||
this.render();
|
||||
if (!this.editing) {
|
||||
this.render();
|
||||
}
|
||||
if (this.otpValue && timeLeft) {
|
||||
this.otpTimeout = setTimeout(this.requestOtpUpdate.bind(this), timeLeft);
|
||||
if (!this.otpTickInterval) {
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
</div>
|
||||
<div class="modal__buttons">
|
||||
{{#each buttons as |btn|}}
|
||||
<button class="{{#unless btn.result}}btn-error{{/unless}} {{#if btn.error}}btn-error{{/if}}"
|
||||
<button class="{{#unless btn.result}}btn-error{{/unless}} {{#if btn.error}}btn-error{{/if}} {{#if btn.silent}}btn-silent{{/if}}"
|
||||
data-result="{{btn.result}}">{{btn.title}}</button>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue