/* Copyright 2013-2015 Josh Drummond This file is part of WebPasswordSafe. WebPasswordSafe is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. WebPasswordSafe 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 General Public License for more details. You should have received a copy of the GNU General Public License along with WebPasswordSafe; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package net.webpasswordsafe.client.ui; import net.webpasswordsafe.client.ClientSessionUtil; import net.webpasswordsafe.client.WebPasswordSafe; import net.webpasswordsafe.client.i18n.TextMessages; import net.webpasswordsafe.client.remote.UserService; import net.webpasswordsafe.common.model.UserAuthnTOTP; import net.webpasswordsafe.common.util.Utils; import com.extjs.gxt.ui.client.Style.HorizontalAlignment; import com.extjs.gxt.ui.client.event.BaseEvent; import com.extjs.gxt.ui.client.event.ButtonEvent; import com.extjs.gxt.ui.client.event.Events; import com.extjs.gxt.ui.client.event.Listener; import com.extjs.gxt.ui.client.event.SelectionListener; import com.extjs.gxt.ui.client.widget.Html; import com.extjs.gxt.ui.client.widget.Info; import com.extjs.gxt.ui.client.widget.Window; import com.extjs.gxt.ui.client.widget.button.Button; import com.extjs.gxt.ui.client.widget.form.CheckBox; import com.extjs.gxt.ui.client.widget.form.LabelField; import com.extjs.gxt.ui.client.widget.form.TextField; import com.extjs.gxt.ui.client.widget.layout.AbsoluteData; import com.extjs.gxt.ui.client.widget.layout.AbsoluteLayout; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.user.client.rpc.AsyncCallback; /** * @author Josh Drummond * */ public class TwoStepVerificationDialog extends Window { private final static TextMessages textMessages = GWT.create(TextMessages.class); private TextField<String> keyTextBox; private UserAuthnTOTP userAuthnTOTP; private CheckBox enabledCheckBox; private Html qrCodeDiv; private JavaScriptObject qrCodeJS; public TwoStepVerificationDialog() { this.setHeading(textMessages.twoStepVerification()); this.setModal(true); this.setLayout(new AbsoluteLayout()); this.setSize(375, 387); this.setResizable(false); LabelField lblfldDesc = new LabelField(textMessages.twoStepKeyInstructions()); add(lblfldDesc, new AbsoluteData(7, 6)); enabledCheckBox = new CheckBox(); enabledCheckBox.addListener(Events.OnClick, new Listener<BaseEvent>() { @Override public void handleEvent(BaseEvent be) { doFirstTimeKey(); } }); enabledCheckBox.setBoxLabel(textMessages.enabled()); add(enabledCheckBox, new AbsoluteData(82, 34)); Button generateButton = new Button(textMessages.newKey(), new SelectionListener<ButtonEvent>() { @Override public void componentSelected(ButtonEvent ce) { doGenerateKey(); } }); add(generateButton, new AbsoluteData(290, 62)); generateButton.setSize("60px", "22px"); LabelField lblfldKey = new LabelField(textMessages.key_()); add(lblfldKey, new AbsoluteData(7, 62)); keyTextBox = new TextField<String>(); keyTextBox.setReadOnly(true); add(keyTextBox, new AbsoluteData(82, 62)); keyTextBox.setSize("200px", "22px"); qrCodeDiv = new Html("<div id=\"qrcode\"></div>"); add(qrCodeDiv, new AbsoluteData(82, 90)); Button saveButton = new Button(textMessages.save(), new SelectionListener<ButtonEvent>() { @Override public void componentSelected(ButtonEvent ce) { doSave(); } }); Button cancelButton = new Button(textMessages.cancel(), new SelectionListener<ButtonEvent>() { @Override public void componentSelected(ButtonEvent ce) { doCancel(); } }); setButtonAlign(HorizontalAlignment.CENTER); addButton(saveButton); addButton(cancelButton); doLoadTwoStepVerificationInfo(); } private void doFirstTimeKey() { if (enabledCheckBox.getValue() && Utils.safeString(keyTextBox.getValue()).equals("")) { doGenerateKey(); } } @Override public void show() { super.show(); } private void refreshKey(String key) { key = Utils.safeString(key); keyTextBox.setValue(key); if (!key.equals("")) { if (null == qrCodeJS) { qrCodeJS = createQRCode(getQRBarcodeURL()); } else { updateQRCode(qrCodeJS, getQRBarcodeURL()); } } } private String getQRBarcodeURL() { return getQRBarcodeURL(ClientSessionUtil.getInstance().getLoggedInUser().getUsername(), "WebPasswordSafe", Utils.safeString(keyTextBox.getValue())); } private String getQRBarcodeURL(String user, String issuer, String secret) { return "otpauth://totp/"+issuer+":"+user+"?secret="+secret+"&issuer="+issuer; } private static native JavaScriptObject createQRCode(String url) /*-{ var qrcode = new $wnd.QRCode("qrcode", { text: url, width: 200, height: 200, colorDark: "#000000", colorLight: "#ffffff", correctLevel: $wnd.QRCode.CorrectLevel.M }); return qrcode; }-*/; private static native void updateQRCode(JavaScriptObject qrcode, String url) /*-{ qrcode.makeCode(url); }-*/; private void setFields() { enabledCheckBox.setValue(userAuthnTOTP.isEnabled()); refreshKey(userAuthnTOTP.getKey()); } private void doLoadTwoStepVerificationInfo() { AsyncCallback<UserAuthnTOTP> callback = new AsyncCallback<UserAuthnTOTP>() { @Override public void onFailure(Throwable caught) { WebPasswordSafe.handleServerFailure(caught); } @Override public void onSuccess(UserAuthnTOTP result) { userAuthnTOTP = result; setFields(); } }; UserService.Util.getInstance().getCurrentUserTOTP(callback); } private void doGenerateKey() { AsyncCallback<String> callback = new AsyncCallback<String>() { @Override public void onFailure(Throwable caught) { WebPasswordSafe.handleServerFailure(caught); } @Override public void onSuccess(String result) { refreshKey(result); } }; UserService.Util.getInstance().generateTOTPKey(callback); } private boolean validateFields() { return true; } private void doSave() { if (validateFields()) { userAuthnTOTP.setKey(Utils.safeString(keyTextBox.getValue())); userAuthnTOTP.setEnabled(enabledCheckBox.getValue()); final AsyncCallback<Void> callback = new AsyncCallback<Void>() { @Override public void onFailure(Throwable caught) { WebPasswordSafe.handleServerFailure(caught); } @Override public void onSuccess(Void result) { Info.display(textMessages.status(), textMessages.twoStepVerificationSaved()); hide(); } }; UserService.Util.getInstance().updateCurrentUserTOTP(userAuthnTOTP, callback); } } private void doCancel() { hide(); } }