summaryrefslogtreecommitdiffstats
path: root/public
diff options
context:
space:
mode:
authorJonas Franz <info@jonasfranz.software>2018-05-19 16:12:37 +0200
committerLauris BH <lauris@nix.lv>2018-05-19 17:12:37 +0300
commit951309f76aab22e3742e8872bf0642fcea2570ae (patch)
tree041e43fcc393d0ca07e4e274b28c1938e6604780 /public
parentf933bcdfeef359d8d9592dc0cf0aea244963e23c (diff)
downloadgitea-951309f76aab22e3742e8872bf0642fcea2570ae.tar.gz
gitea-951309f76aab22e3742e8872bf0642fcea2570ae.zip
Add support for FIDO U2F (#3971)
* Add support for U2F Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add vendor library Add missing translations Signed-off-by: Jonas Franz <info@jonasfranz.software> * Minor improvements Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add U2F support for Firefox, Chrome (Android) by introducing a custom JS library Add U2F error handling Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add U2F login page to OAuth Signed-off-by: Jonas Franz <info@jonasfranz.software> * Move U2F user settings to a separate file Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add unit tests for u2f model Renamed u2f table name Signed-off-by: Jonas Franz <info@jonasfranz.software> * Fix problems caused by refactoring Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add U2F documentation Signed-off-by: Jonas Franz <info@jonasfranz.software> * Remove not needed console.log-s Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add default values to app.ini.sample Add FIDO U2F to comparison Signed-off-by: Jonas Franz <info@jonasfranz.software>
Diffstat (limited to 'public')
-rw-r--r--public/js/index.js128
-rw-r--r--public/vendor/librejs.html5
-rw-r--r--public/vendor/plugins/u2f/index.js1
3 files changed, 133 insertions, 1 deletions
diff --git a/public/js/index.js b/public/js/index.js
index e826c2f3f3..e98a3fe6de 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -1432,6 +1432,130 @@ function initCodeView() {
}
}
+function initU2FAuth() {
+ if($('#wait-for-key').length === 0) {
+ return
+ }
+ u2fApi.ensureSupport()
+ .then(function () {
+ $.getJSON('/user/u2f/challenge').success(function(req) {
+ u2fApi.sign(req.appId, req.challenge, req.registeredKeys, 30)
+ .then(u2fSigned)
+ .catch(function (err) {
+ if(err === undefined) {
+ u2fError(1);
+ return
+ }
+ u2fError(err.metaData.code);
+ });
+ });
+ }).catch(function () {
+ // Fallback in case browser do not support U2F
+ window.location.href = "/user/two_factor"
+ })
+}
+function u2fSigned(resp) {
+ $.ajax({
+ url:'/user/u2f/sign',
+ type:"POST",
+ headers: {"X-Csrf-Token": csrf},
+ data: JSON.stringify(resp),
+ contentType:"application/json; charset=utf-8",
+ }).done(function(res){
+ window.location.replace(res);
+ }).fail(function (xhr, textStatus) {
+ u2fError(1);
+ });
+}
+
+function u2fRegistered(resp) {
+ if (checkError(resp)) {
+ return;
+ }
+ $.ajax({
+ url:'/user/settings/security/u2f/register',
+ type:"POST",
+ headers: {"X-Csrf-Token": csrf},
+ data: JSON.stringify(resp),
+ contentType:"application/json; charset=utf-8",
+ success: function(){
+ window.location.reload();
+ },
+ fail: function (xhr, textStatus) {
+ u2fError(1);
+ }
+ });
+}
+
+function checkError(resp) {
+ if (!('errorCode' in resp)) {
+ return false;
+ }
+ if (resp.errorCode === 0) {
+ return false;
+ }
+ u2fError(resp.errorCode);
+ return true;
+}
+
+
+function u2fError(errorType) {
+ var u2fErrors = {
+ 'browser': $('#unsupported-browser'),
+ 1: $('#u2f-error-1'),
+ 2: $('#u2f-error-2'),
+ 3: $('#u2f-error-3'),
+ 4: $('#u2f-error-4'),
+ 5: $('.u2f-error-5')
+ };
+ u2fErrors[errorType].removeClass('hide');
+ for(var type in u2fErrors){
+ if(type != errorType){
+ u2fErrors[type].addClass('hide');
+ }
+ }
+ $('#u2f-error').modal('show');
+}
+
+function initU2FRegister() {
+ $('#register-device').modal({allowMultiple: false});
+ $('#u2f-error').modal({allowMultiple: false});
+ $('#register-security-key').on('click', function(e) {
+ e.preventDefault();
+ u2fApi.ensureSupport()
+ .then(u2fRegisterRequest)
+ .catch(function() {
+ u2fError('browser');
+ })
+ })
+}
+
+function u2fRegisterRequest() {
+ $.post("/user/settings/security/u2f/request_register", {
+ "_csrf": csrf,
+ "name": $('#nickname').val()
+ }).success(function(req) {
+ $("#nickname").closest("div.field").removeClass("error");
+ $('#register-device').modal('show');
+ if(req.registeredKeys === null) {
+ req.registeredKeys = []
+ }
+ u2fApi.register(req.appId, req.registerRequests, req.registeredKeys, 30)
+ .then(u2fRegistered)
+ .catch(function (reason) {
+ if(reason === undefined) {
+ u2fError(1);
+ return
+ }
+ u2fError(reason.metaData.code);
+ });
+ }).fail(function(xhr, status, error) {
+ if(xhr.status === 409) {
+ $("#nickname").closest("div.field").addClass("error");
+ }
+ });
+}
+
$(document).ready(function () {
csrf = $('meta[name=_csrf]').attr("content");
suburl = $('meta[name=_suburl]').attr("content");
@@ -1643,6 +1767,8 @@ $(document).ready(function () {
initCtrlEnterSubmit();
initNavbarContentToggle();
initTopicbar();
+ initU2FAuth();
+ initU2FRegister();
// Repo clone url.
if ($('#repo-clone-url').length > 0) {
@@ -2201,7 +2327,7 @@ function initTopicbar() {
return
}
var topicArray = topics.split(",");
-
+
var last = viewDiv.children("a").last();
for (var i=0;i < topicArray.length; i++) {
$('<div class="ui green basic label topic" style="cursor:pointer;">'+topicArray[i]+'</div>').insertBefore(last)
diff --git a/public/vendor/librejs.html b/public/vendor/librejs.html
index 68586064c6..e24bb9c3eb 100644
--- a/public/vendor/librejs.html
+++ b/public/vendor/librejs.html
@@ -110,6 +110,11 @@
<td><a href="https://github.com/mozilla/pdf.js/blob/master/LICENSE">Apache-2.0-only</a></td>
<td><a href="https://github.com/mozilla/pdf.js/archive/v1.4.20.tar.gz">pdf.js-v1.4.20.tar.gz</a></td>
</tr>
+ <tr>
+ <td><a href="/vendor/plugins/u2f/">u2f-api</a></td>
+ <td><a href="https://github.com/go-gitea/u2f-api/blob/master/LICENSE">Expat</a></td>
+ <td><a href="https://github.com/go-gitea/u2f-api/archive/v1.0.8.zip">u2f-api-1.0.8.zip</a></td>
+ </tr>
<tr>
<td><a href="/vendor/assets/font-awesome/fonts/">font-awesome - fonts</a></td>
<td><a href="http://fontawesome.io/license/">OFL</a></td>
diff --git a/public/vendor/plugins/u2f/index.js b/public/vendor/plugins/u2f/index.js
new file mode 100644
index 0000000000..1413f51d7b
--- /dev/null
+++ b/public/vendor/plugins/u2f/index.js
@@ -0,0 +1 @@
+this.u2fApi=function(e){var t={};function r(o){if(t[o])return t[o].exports;var n=t[o]={i:o,l:!1,exports:{}};return e[o].call(n.exports,n,n.exports,r),n.l=!0,n.exports}return r.m=e,r.c=t,r.d=function(e,t,o){r.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:o})},r.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=1)}([function(e,t,r){"use strict";var o,n=n||{};e.exports=n,n.EXTENSION_ID="kmendfapggjehodndflmmgagdbamhnfd",n.MessageTypes={U2F_REGISTER_REQUEST:"u2f_register_request",U2F_REGISTER_RESPONSE:"u2f_register_response",U2F_SIGN_REQUEST:"u2f_sign_request",U2F_SIGN_RESPONSE:"u2f_sign_response",U2F_GET_API_VERSION_REQUEST:"u2f_get_api_version_request",U2F_GET_API_VERSION_RESPONSE:"u2f_get_api_version_response"},n.ErrorCodes={OK:0,OTHER_ERROR:1,BAD_REQUEST:2,CONFIGURATION_UNSUPPORTED:3,DEVICE_INELIGIBLE:4,TIMEOUT:5},n.U2fRequest,n.U2fResponse,n.Error,n.Transport,n.Transports,n.SignRequest,n.SignResponse,n.RegisterRequest,n.RegisterResponse,n.RegisteredKey,n.GetJsApiVersionResponse,n.getMessagePort=function(e){if("undefined"!=typeof chrome&&chrome.runtime){var t={type:n.MessageTypes.U2F_SIGN_REQUEST,signRequests:[]};chrome.runtime.sendMessage(n.EXTENSION_ID,t,function(){chrome.runtime.lastError?n.getIframePort_(e):n.getChromeRuntimePort_(e)})}else n.isAndroidChrome_()?n.getAuthenticatorPort_(e):n.isIosChrome_()?n.getIosPort_(e):n.getIframePort_(e)},n.isAndroidChrome_=function(){var e=navigator.userAgent;return-1!=e.indexOf("Chrome")&&-1!=e.indexOf("Android")},n.isIosChrome_=function(){return["iPhone","iPad","iPod"].indexOf(navigator.platform)>-1},n.getChromeRuntimePort_=function(e){var t=chrome.runtime.connect(n.EXTENSION_ID,{includeTlsChannelId:!0});setTimeout(function(){e(new n.WrappedChromeRuntimePort_(t))},0)},n.getAuthenticatorPort_=function(e){setTimeout(function(){e(new n.WrappedAuthenticatorPort_)},0)},n.getIosPort_=function(e){setTimeout(function(){e(new n.WrappedIosPort_)},0)},n.WrappedChromeRuntimePort_=function(e){this.port_=e},n.formatSignRequest_=function(e,t,r,s,i){if(void 0===o||o<1.1){for(var a=[],u=0;u<r.length;u++)a[u]={version:r[u].version,challenge:t,keyHandle:r[u].keyHandle,appId:e};return{type:n.MessageTypes.U2F_SIGN_REQUEST,signRequests:a,timeoutSeconds:s,requestId:i}}return{type:n.MessageTypes.U2F_SIGN_REQUEST,appId:e,challenge:t,registeredKeys:r,timeoutSeconds:s,requestId:i}},n.formatRegisterRequest_=function(e,t,r,s,i){if(void 0===o||o<1.1){for(var a=0;a<r.length;a++)r[a].appId=e;var u=[];for(a=0;a<t.length;a++)u[a]={version:t[a].version,challenge:r[0],keyHandle:t[a].keyHandle,appId:e};return{type:n.MessageTypes.U2F_REGISTER_REQUEST,signRequests:u,registerRequests:r,timeoutSeconds:s,requestId:i}}return{type:n.MessageTypes.U2F_REGISTER_REQUEST,appId:e,registerRequests:r,registeredKeys:t,timeoutSeconds:s,requestId:i}},n.WrappedChromeRuntimePort_.prototype.postMessage=function(e){this.port_.postMessage(e)},n.WrappedChromeRuntimePort_.prototype.addEventListener=function(e,t){var r=e.toLowerCase();"message"==r||"onmessage"==r?this.port_.onMessage.addListener(function(e){t({data:e})}):console.error("WrappedChromeRuntimePort only supports onMessage")},n.WrappedAuthenticatorPort_=function(){this.requestId_=-1,this.requestObject_=null},n.WrappedAuthenticatorPort_.prototype.postMessage=function(e){var t=n.WrappedAuthenticatorPort_.INTENT_URL_BASE_+";S.request="+encodeURIComponent(JSON.stringify(e))+";end";document.location=t},n.WrappedAuthenticatorPort_.prototype.getPortType=function(){return"WrappedAuthenticatorPort_"},n.WrappedAuthenticatorPort_.prototype.addEventListener=function(e,t){if("message"==e.toLowerCase()){window.addEventListener("message",this.onRequestUpdate_.bind(this,t),!1)}else console.error("WrappedAuthenticatorPort only supports message")},n.WrappedAuthenticatorPort_.prototype.onRequestUpdate_=function(e,t){var r=JSON.parse(t.data),o=(r.intentURL,r.errorCode,null);r.hasOwnProperty("data")&&(o=JSON.parse(r.data)),e({data:o})},n.WrappedAuthenticatorPort_.INTENT_URL_BASE_="intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE",n.WrappedIosPort_=function(){},n.WrappedIosPort_.prototype.postMessage=function(e){var t=JSON.stringify(e),r="u2f://auth?"+encodeURI(t);location.replace(r)},n.WrappedIosPort_.prototype.getPortType=function(){return"WrappedIosPort_"},n.WrappedIosPort_.prototype.addEventListener=function(e,t){"message"!==e.toLowerCase()&&console.error("WrappedIosPort only supports message")},n.getIframePort_=function(e){var t="chrome-extension://"+n.EXTENSION_ID,r=document.createElement("iframe");r.src=t+"/u2f-comms.html",r.setAttribute("style","display:none"),document.body.appendChild(r);var o=new MessageChannel,s=function(t){"ready"==t.data?(o.port1.removeEventListener("message",s),e(o.port1)):console.error('First event on iframe port was not "ready"')};o.port1.addEventListener("message",s),o.port1.start(),r.addEventListener("load",function(){r.contentWindow.postMessage("init",t,[o.port2])})},n.EXTENSION_TIMEOUT_SEC=30,n.port_=null,n.waitingForPort_=[],n.reqCounter_=0,n.callbackMap_={},n.getPortSingleton_=function(e){n.port_?e(n.port_):(0==n.waitingForPort_.length&&n.getMessagePort(function(e){for(n.port_=e,n.port_.addEventListener("message",n.responseHandler_);n.waitingForPort_.length;)n.waitingForPort_.shift()(n.port_)}),n.waitingForPort_.push(e))},n.responseHandler_=function(e){var t=e.data,r=t.requestId;if(r&&n.callbackMap_[r]){var o=n.callbackMap_[r];delete n.callbackMap_[r],o(t.responseData)}else console.error("Unknown or missing requestId in response.")},n.isSupported=function(e){var t=!1;function r(r){t||(t=!0,e(r))}n.getApiVersion(function(e){o=void 0===e.js_api_version?0:e.js_api_version,r(!0)}),setTimeout(r.bind(null,!1),500)},n.sign=function(e,t,r,s,i){void 0===o?n.getApiVersion(function(a){o=void 0===a.js_api_version?0:a.js_api_version,console.log("Extension JS API Version: ",o),n.sendSignRequest(e,t,r,s,i)}):n.sendSignRequest(e,t,r,s,i)},n.sendSignRequest=function(e,t,r,o,s){n.getPortSingleton_(function(i){var a=++n.reqCounter_;n.callbackMap_[a]=o;var u=void 0!==s?s:n.EXTENSION_TIMEOUT_SEC,p=n.formatSignRequest_(e,t,r,u,a);i.postMessage(p)})},n.register=function(e,t,r,s,i){void 0===o?n.getApiVersion(function(a){o=void 0===a.js_api_version?0:a.js_api_version,console.log("Extension JS API Version: ",o),n.sendRegisterRequest(e,t,r,s,i)}):n.sendRegisterRequest(e,t,r,s,i)},n.sendRegisterRequest=function(e,t,r,o,s){n.getPortSingleton_(function(i){var a=++n.reqCounter_;n.callbackMap_[a]=o;var u=void 0!==s?s:n.EXTENSION_TIMEOUT_SEC,p=n.formatRegisterRequest_(e,r,t,u,a);i.postMessage(p)})},n.getApiVersion=function(e,t){n.getPortSingleton_(function(r){if(r.getPortType){var o;switch(r.getPortType()){case"WrappedIosPort_":case"WrappedAuthenticatorPort_":o=1.1;break;default:o=0}e({js_api_version:o})}else{var s=++n.reqCounter_;n.callbackMap_[s]=e;var i={type:n.MessageTypes.U2F_GET_API_VERSION_REQUEST,timeoutSeconds:void 0!==t?t:n.EXTENSION_TIMEOUT_SEC,requestId:s};r.postMessage(i)}})}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var o=r(0),n="undefined"!=typeof navigator&&!!navigator.userAgent,s=n&&navigator.userAgent.match(/Safari\//)&&!navigator.userAgent.match(/Chrome\//),i=n&&navigator.userAgent.match(/Edge\/1[2345]/),a=null;function u(){return a||(a=new Promise(function(e,t){function r(){e({u2f:null})}return n?s?r():void 0!==window.u2f&&"function"==typeof window.u2f.sign?e({u2f:window.u2f}):i?r():"http:"===location.protocol?r():"undefined"==typeof MessageChannel?r():void o.isSupported(function(t){t?e({u2f:o}):r()}):r()})),a}function p(e,r){var o=null!=r?r.errorCode:1,n=t.ErrorNames[""+o],s=new Error(e);return s.metaData={type:n,code:o},s}function d(e){if(!e.u2f){if("http:"===location.protocol)throw new Error("U2F isn't supported over http, only https");throw new Error("U2F not supported")}}t.ErrorCodes={OK:0,OTHER_ERROR:1,BAD_REQUEST:2,CONFIGURATION_UNSUPPORTED:3,DEVICE_INELIGIBLE:4,TIMEOUT:5},t.ErrorNames={0:"OK",1:"OTHER_ERROR",2:"BAD_REQUEST",3:"CONFIGURATION_UNSUPPORTED",4:"DEVICE_INELIGIBLE",5:"TIMEOUT"},t.isSupported=function(){return u().then(function(e){return!!e.u2f})},t.ensureSupport=function(){return u().then(d)},t.register=function(e,t,r,o){return Array.isArray(t)||(t=[t]),"number"==typeof r&&void 0===o&&(o=r,r=null),r||(r=[]),u().then(function(n){d(n);var s=n.u2f;return new Promise(function(n,i){s.register(e,t,r,function(e){e.errorCode?i(p("Registration failed",e)):(delete e.errorCode,n(e))},o)})})},t.sign=function(e,t,r,o){return Array.isArray(r)||(r=[r]),u().then(function(n){d(n);var s=n.u2f;return new Promise(function(n,i){s.sign(e,t,r,function(e){e.errorCode?i(p("Sign failed",e)):(delete e.errorCode,n(e))},o)})})}}]);