From 14b1b598c0bd0461071e98c706bb9abea85c9558 Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Mon, 8 Nov 2021 09:49:52 +1300 Subject: [PATCH] DEP PHP 8 support --- client/dist/js/bundle.js | 2 +- client/src/lib/convert.js | 15 +++++++++++---- client/src/lib/tests/convert-test.js | 28 ++++++++++++++++++++++++++++ composer.json | 3 ++- src/BaseHandlerTrait.php | 8 ++++++-- src/RegisterHandler.php | 19 +++++++------------ src/VerifyHandler.php | 18 +++++++++--------- tests/RegisterHandlerTest.php | 3 +++ tests/VerifyHandlerTest.php | 4 ++++ 9 files changed, 71 insertions(+), 29 deletions(-) create mode 100644 client/src/lib/tests/convert-test.js diff --git a/client/dist/js/bundle.js b/client/dist/js/bundle.js index 7f82fe2..8dbf2b8 100644 --- a/client/dist/js/bundle.js +++ b/client/dist/js/bundle.js @@ -1 +1 @@ -!function(e){function t(r){if(n[r])return n[r].exports;var a=n[r]={i:r,l:!1,exports:{}};return e[r].call(a.exports,a,a.exports,t),a.l=!0,a.exports}var n={};t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s="./client/src/bundles/bundle.js")}({"./client/lang/src/en.json":function(e,t){e.exports={"MFAWebAuthnRegister.BACK":"Back","MFAWebAuthnRegister.COMPLETEREGISTRATION":"Complete registration","MFAWebAuthnRegister.DESCRIPTION":"Contact your administrator if you require a security key. ","MFAWebAuthnRegister.FAILURE":"Something went wrong. Please re-insert your key and try again","MFAWebAuthnRegister.HELP":"How to use security keys.","MFAWebAuthnRegister.INSTRUCTION":"Insert security key and press {button}","MFAWebAuthnRegister.REGISTER":"Register key","MFAWebAuthnRegister.REGISTERING":"Registering","MFAWebAuthnRegister.RETRY":"Retry","MFAWebAuthnRegister.SUCCESS":"Key verified","MFAWebAuthnRegister.WAITING":"Waiting","MFAWebAuthnVerify.DESCRIPTION":"Use your security key to verify your identity.","MFAWebAuthnVerify.FAILURE":"Something went wrong, please re-insert your key and try again","MFAWebAuthnVerify.HELP":"How to use security keys.","MFAWebAuthnVerify.INSTRUCTION":"Please insert your security key and {button}.","MFAWebAuthnVerify.RETRY":"Try again","MFAWebAuthnVerify.SUCCESS":"Logging in...","MFAWebAuthnVerify.VERIFY":"activate it","MFAWebAuthnVerify.WAITING":"Waiting..."}},"./client/src/boot/index.js":function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var a=n("./client/src/boot/registerComponents.js"),s=r(a),i=n("./client/src/boot/registerReducers.js"),o=r(i);window.document.addEventListener("DOMContentLoaded",function(){(0,s.default)(),(0,o.default)()})},"./client/src/boot/registerComponents.js":function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(0),s=r(a),i=n("./client/src/components/WebAuthn/Register.js"),o=r(i),u=n("./client/src/components/WebAuthn/Verify.js"),c=r(u);t.default=function(){s.default.component.registerMany({WebAuthnRegister:o.default,WebAuthnVerify:c.default})}},"./client/src/boot/registerReducers.js":function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(0),s=r(a),i=n("./client/src/state/webauthnAvailableReducer.js"),o=r(i);t.default=function(){s.default.reducer.register("web-authnAvailability",o.default)}},"./client/src/bundles/bundle.js":function(e,t,n){"use strict";n("./client/src/boot/index.js")},"./client/src/components/Icons/ActivateToken.js":function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n("./node_modules/react/index.js"),a=function(e){return e&&e.__esModule?e:{default:e}}(r);t.default=function(){return a.default.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 240 240"},a.default.createElement("defs",null,a.default.createElement("style",null,".a{fill:none;}.b{fill:#549ad3;}.c,.g{fill:#333a48;}.d{fill:#ffcf6e;}.e{clip-path:url(#a);}.f{fill:#d6e1ea;}.g{opacity:0.2;}.h{fill:url(#b);}.i{fill:#fff;}"),a.default.createElement("clipPath",{id:"a"},a.default.createElement("circle",{className:"a",cx:"120",cy:"120",r:"120"})),a.default.createElement("linearGradient",{id:"b",x1:"-26.14",y1:"68.86",x2:"-11.09",y2:"-38.22",gradientUnits:"userSpaceOnUse"},a.default.createElement("stop",{offset:"0.58",stopColor:"#333a48"}),a.default.createElement("stop",{offset:"0.59",stopColor:"#424a5c"}))),a.default.createElement("title",null,"U2F"),a.default.createElement("circle",{className:"b",cx:"120",cy:"120",r:"120"}),a.default.createElement("path",{className:"c",d:"M127.91,93.4a1.22,1.22,0,0,0,1.21,1.22h35.61a3.77,3.77,0,0,0,3.56-4V74.33a3.77,3.77,0,0,0-3.56-4H129.12a1.22,1.22,0,0,0-1.21,1.22Zm34.38-13.32A2.22,2.22,0,1,1,160,82.3,2.24,2.24,0,0,1,162.29,80.08Z"}),a.default.createElement("path",{className:"c",d:"M114.18,76.44h15.35a0,0,0,0,1,0,0v9.12a3,3,0,0,1-3,3h-9.35a3,3,0,0,1-3-3V76.44A0,0,0,0,1,114.18,76.44Z",transform:"translate(204.35 -39.35) rotate(90)"}),a.default.createElement("circle",{className:"d",cx:"146.94",cy:"82.77",r:"7.27"}),a.default.createElement("rect",{className:"d",x:"121.65",y:"82.3",width:"2.02",height:"10.5",transform:"translate(210.21 -35.11) rotate(90)"}),a.default.createElement("rect",{className:"d",x:"122.46",y:"79.87",width:"2.02",height:"8.88",transform:"translate(207.78 -39.15) rotate(90)"}),a.default.createElement("rect",{className:"d",x:"121.65",y:"72.2",width:"2.02",height:"10.5",transform:"translate(200.11 -45.21) rotate(90)"}),a.default.createElement("rect",{className:"d",x:"122.46",y:"76.24",width:"2.02",height:"8.88",transform:"translate(204.15 -42.78) rotate(90)"}),a.default.createElement("g",{className:"e"},a.default.createElement("rect",{className:"f",x:"-157.14",y:"54",width:"285",height:"192",rx:"10",ry:"10"}),a.default.createElement("rect",{className:"c",x:"-139.64",y:"70",width:"250",height:"97",rx:"2",ry:"2"}),a.default.createElement("rect",{className:"g",x:"-62.64",y:"175",width:"96",height:"62",rx:"3",ry:"3"}),a.default.createElement("rect",{className:"c",x:"-139.64",y:"54",width:"250",height:"5"}),a.default.createElement("path",{className:"h",d:"M-149.51,51.5a2.52,2.52,0,0,1-2.25-1.41l-30.14-62a2.49,2.49,0,0,1,.13-2.42,2.47,2.47,0,0,1,2.12-1.17h330a2.47,2.47,0,0,1,2.12,1.17,2.47,2.47,0,0,1,.13,2.42l-30.14,62a2.5,2.5,0,0,1-2.25,1.41Z"}),a.default.createElement("path",{className:"c",d:"M150.37-13h0L120.23,49H-149.51l-30.14-62h330m0-5h-330a5,5,0,0,0-4.49,7.19l30.13,62a5,5,0,0,0,4.5,2.81H120.23a5,5,0,0,0,4.5-2.81l30.14-62a5,5,0,0,0-4.5-7.19Z"})),a.default.createElement("path",{className:"i",d:"M159,107.75a5.47,5.47,0,0,1,1.83.32,5.64,5.64,0,0,1,3.76,5.37c0,2.26,0,4.51,0,6.77v18.2a.6.6,0,0,0,.61.6.57.57,0,0,0,.5-.29c.89-1.49,2.34-3.08,4.49-3.08a6,6,0,0,1,4.32,1.5,5.45,5.45,0,0,1,1.43,2.18,1.28,1.28,0,0,0,1.2.86,1.2,1.2,0,0,0,.67-.21,5.81,5.81,0,0,1,3.2-1,5.33,5.33,0,0,1,2.77.76,5.81,5.81,0,0,1,2.43,3,5,5,0,0,1,.28,1.19,1.05,1.05,0,0,0,1,.93,1.07,1.07,0,0,0,.43-.09,5.53,5.53,0,0,1,2.4-.55h.08a5.57,5.57,0,0,1,5.45,4.66,17.12,17.12,0,0,1,.07,2.89v7.1c0,3.09-2.59,29-18,29H161.34l-1.1,0a4,4,0,0,1-1.42-.21A4.63,4.63,0,0,1,157,186a6,6,0,0,1-.5-.71L137,153.54a4.89,4.89,0,0,1,3.92-7.27h.22a5,5,0,0,1,3.86,1.86,8.76,8.76,0,0,1,.79,1.22l2.55,4.42,1.72,3a1.72,1.72,0,0,0,1.52.89,1.77,1.77,0,0,0,1.81-1.74c0-.73,0-1.26,0-1.42V113.52a5.62,5.62,0,0,1,5.61-5.77m0-5h0a10.6,10.6,0,0,0-10.61,10.75v30.94a10.12,10.12,0,0,0-7.28-3.18h-.47a9.9,9.9,0,0,0-8,14.77l0,.06,0,.05,19.47,31.75a12.19,12.19,0,0,0,4.79,4.45,8.84,8.84,0,0,0,3.29.57h17.65c18.94,0,23-27.28,23-34v-7.1c0-.4,0-.73,0-1a12.7,12.7,0,0,0-.16-2.75,10.55,10.55,0,0,0-10.37-8.8H190a10.89,10.89,0,0,0-3.7-3.75A10.45,10.45,0,0,0,181,134a10.69,10.69,0,0,0-2.34.25,10.59,10.59,0,0,0-.74-.76,10.91,10.91,0,0,0-7.74-2.85h-.6V118c0-1.51,0-3,0-4.55a10.61,10.61,0,0,0-7.09-10.06,10.38,10.38,0,0,0-3.5-.6Z"}))}},"./client/src/components/Icons/CircleTick.js":function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n("./node_modules/react/index.js"),a=function(e){return e&&e.__esModule?e:{default:e}}(r);t.default=function(e){var t=e.color,n=void 0===t?"currentColor":t,r=e.size,s=void 0===r?"3em":r;return a.default.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512",height:s,width:s},a.default.createElement("g",{fill:n},a.default.createElement("path",{d:"M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"})))}},"./client/src/components/Icons/CircleWarning.js":function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n("./node_modules/react/index.js"),a=function(e){return e&&e.__esModule?e:{default:e}}(r);t.default=function(e){var t=e.color,n=void 0===t?"currentColor":t,r=e.size,s=void 0===r?"3em":r;return a.default.createElement("svg",{width:s,height:s,viewBox:"0 0 80 80",xmlns:"http://www.w3.org/2000/svg"},a.default.createElement("g",{fill:n,fillRule:"nonzero"},a.default.createElement("path",{d:"M39.8233243,0 C17.9664349,0 0.272762495,17.8947368 0.272762495,40 C0.272762495,62.1052632 17.9664349,80 39.8233243,80 C61.6802137,80 79.3738861,62.1052632 79.3738861,40 C79.3738861,17.8947368 61.6802137,0 39.8233243,0 Z M44.5069435,62.6315789 C43.2926718,63.8596491 41.7314654,64.5614035 40.170259,64.5614035 C38.4355853,64.5614035 36.8743789,63.8596491 35.8335746,62.6315789 C34.619303,61.5789474 33.9254335,60 33.9254335,58.245614 C33.9254335,56.6666667 34.619303,55.0877193 35.8335746,53.8596491 C38.0886505,51.5789474 42.2518676,51.5789474 44.5069435,53.8596491 C45.7212151,55.0877193 46.2416172,56.6666667 46.2416172,58.245614 C46.4150846,60 45.7212151,61.5789474 44.5069435,62.6315789 Z M47.2824215,23.3333333 L45.0273456,44.0350877 C44.8538782,46.8421053 42.4253349,48.7719298 39.6498569,48.5964912 C37.2213136,48.245614 35.4866399,46.4912281 35.3131725,44.0350877 L33.0580966,23.3333333 C32.7111619,19.2982456 35.4866399,15.7894737 39.4763895,15.2631579 C43.4661392,14.9122807 46.9354867,17.8947368 47.2824215,21.9298246 C47.4558889,22.2807018 47.4558889,22.8070175 47.2824215,23.3333333 Z"})))}},"./client/src/components/LoadingIndicator.js":function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n("./node_modules/react/index.js"),s=r(a),i=n("./node_modules/classnames/index.js"),o=r(i);t.default=function(e){var t=e.block,n=void 0!==t&&t,r=e.size,a=void 0===r?"6em":r;return s.default.createElement("div",{style:{height:a,width:a},className:(0,o.default)({"mfa-loading-indicator":!0,"mfa-loading-indicator--block":n})})}},"./client/src/components/WebAuthn/Register.js":function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function s(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0}),t.Component=t.VIEWS=void 0;var o=function(){function e(e,t){for(var n=0;n"+s+""});return c.default.createElement("div",{className:"mfa-registration-container__description"},c.default.createElement("p",null,t._t("MFAWebAuthnRegister.DESCRIPTION",R["MFAWebAuthnRegister.DESCRIPTION"]),r&&c.default.createElement("a",{href:r,target:"_blank",rel:"noopener noreferrer"},a||t._t("MFAWebAuthnRegister.HELP",R["MFAWebAuthnRegister.HELP"]))),c.default.createElement("p",{dangerouslySetInnerHTML:{__html:i}}))}},{key:"renderStatus",value:function(){var e=this.props.errors,t=window,n=t.ss.i18n;switch(this.state.view){case S.READY:return c.default.createElement("div",{className:"mfa-registration-container__status status-message--empty"});case S.PROMPTING:case S.LOADING:default:return c.default.createElement("div",{className:"mfa-registration-container__status status-message--loading"},c.default.createElement(v.default,{size:"3em"}),c.default.createElement("span",{className:"status-message__description"},n._t("MFAWebAuthnRegister.WAITING",R["MFAWebAuthnRegister.WAITING"])));case S.SUCCESS:return c.default.createElement("div",{className:"mfa-registration-container__status status-message--success"},c.default.createElement("span",{className:"status-message__icon"},c.default.createElement(y.default,{size:"32px"})),c.default.createElement("span",{className:"status-message__description"},n._t("MFAWebAuthnRegister.SUCCESS",R["MFAWebAuthnRegister.SUCCESS"])));case S.FAILURE:return c.default.createElement("div",{className:"mfa-registration-container__status status-message--failure"},c.default.createElement("span",{className:"status-message__icon"},c.default.createElement(g.default,{size:"32px"})),c.default.createElement("span",{className:"status-message__description"},n._t("MFAWebAuthnRegister.FAILURE",R["MFAWebAuthnRegister.FAILURE"])));case S.ERROR:return c.default.createElement("div",{className:"mfa-registration-container__status status-message--error"},c.default.createElement("span",{className:"status-message__icon"},c.default.createElement(g.default,{size:"32px"})),c.default.createElement("span",{className:"status-message__description"},e.join(", ")))}}},{key:"renderThumbnail",value:function(){return c.default.createElement("div",{className:"mfa-registration-container__thumbnail"},c.default.createElement(E.default,null))}},{key:"renderActions",value:function(){var e=window,t=e.ss.i18n,n=this.state.view,r=[];switch(n){case S.FAILURE:r=[{action:this.handleStartRegistration,name:t._t("MFAWebAuthnRegister.RETRY",R["MFAWebAuthnRegister.RETRY"])},{action:this.handleBack,name:t._t("MFAWebAuthnRegister.BACK",R["MFAWebAuthnRegister.BACK"])}];break;case S.ERROR:r=[];break;case S.READY:r=[{action:this.handleStartRegistration,name:t._t("MFAWebAuthnRegister.REGISTER",R["MFAWebAuthnRegister.REGISTER"])},{action:this.handleBack,name:t._t("MFAWebAuthnRegister.BACK",R["MFAWebAuthnRegister.BACK"])}];break;case S.PROMPTING:r=[{action:this.handleStartRegistration,name:t._t("MFAWebAuthnRegister.REGISTERING",R["MFAWebAuthnRegister.REGISTERING"]),disabled:!0},{action:this.handleBack,name:t._t("MFAWebAuthnRegister.BACK",R["MFAWebAuthnRegister.BACK"]),disabled:!0}];break;case S.LOADING:default:r=[{action:this.handleStartRegistration,name:t._t("MFAWebAuthnRegister.REGISTERING",R["MFAWebAuthnRegister.REGISTERING"]),disabled:!0},{action:this.handleBack,name:t._t("MFAWebAuthnRegister.BACK",R["MFAWebAuthnRegister.BACK"])}];break;case S.SUCCESS:r=[{action:this.handleNext,name:t._t("MFAWebAuthnRegister.COMPLETEREGISTRATION",R["MFAWebAuthnRegister.COMPLETEREGISTRATION"])}]}return c.default.createElement("div",{className:"mfa-registration-container__actions mfa-action-list"},r.map(function(e,t){var n=0===t,r=(0,m.default)("btn","mfa-action-list__item",{"btn-primary":n,"btn-secondary":!n});return c.default.createElement("button",{key:e.name,className:r,disabled:e.disabled||!1,onClick:e.action,type:"button"},e.name)}))}},{key:"render",value:function(){return c.default.createElement("div",{className:"mfa-registration-container mfa-registration-container--web-authn"},this.renderDescription(),this.renderStatus(),this.renderThumbnail(),this.renderActions())}}]),t}(u.Component);j.propTypes={keyData:f.default.object,method:f.default.object.isRequired,errors:f.default.arrayOf(f.default.string),onBack:f.default.func.isRequired,onCompleteRegistration:f.default.func.isRequired},j.defaultProps={errors:[]},j.displayName="WebAuthnRegister",t.Component=j,t.default=j},"./client/src/components/WebAuthn/Verify.js":function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function s(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var o=function(){function e(e,t){for(var n=0;n"+r+""});return c.default.createElement("div",{className:"mfa-verification-container__description"},c.default.createElement("p",null,t._t("MFAWebAuthnVerify.DESCRIPTION",R["MFAWebAuthnVerify.DESCRIPTION"]),n&&c.default.createElement("a",{href:n,target:"_blank",rel:"noopener noreferrer"},t._t("MFAWebAuthnVerify.HELP",R["MFAWebAuthnVerify.HELP"]))),c.default.createElement("p",{dangerouslySetInnerHTML:{__html:a}}))}},{key:"renderStatus",value:function(){var e=window,t=e.ss.i18n,n=this.props.errors,r=this.state,a=r.failure,s=r.success;return n.length?c.default.createElement("div",{className:"mfa-verification-container__status status-message--error"},c.default.createElement("span",{className:"status-message__icon"},c.default.createElement(g.default,{size:"32px"})),c.default.createElement("span",{className:"status-message__description"},n.join(", "))):s?c.default.createElement("div",{className:"mfa-verification-container__status status-message--success"},c.default.createElement("span",{className:"status-message__icon"},c.default.createElement(y.default,{size:"32px"})),c.default.createElement("span",{className:"status-message__description"},t._t("MFAWebAuthnVerify.SUCCESS",R["MFAWebAuthnVerify.SUCCESS"]))):a?c.default.createElement("div",{className:"mfa-verification-container__status status-message--failure"},c.default.createElement("span",{className:"status-message__icon"},c.default.createElement(g.default,{size:"32px"})),c.default.createElement("span",{className:"status-message__description"},t._t("MFAWebAuthnVerify.FAILURE",R["MFAWebAuthnVerify.FAILURE"]))):c.default.createElement("div",{className:"mfa-verification-container__status status-message--loading"},c.default.createElement(v.default,{size:"3em"}),c.default.createElement("span",{className:"status-message__description"},t._t("MFAWebAuthnVerify.WAITING",R["MFAWebAuthnVerify.WAITING"])))}},{key:"renderThumbnail",value:function(){return c.default.createElement("div",{className:"mfa-verification-container__thumbnail"},c.default.createElement(E.default,null))}},{key:"renderActions",value:function(){var e=window,t=e.ss.i18n,n=this.props.moreOptionsControl,r=this.state,a=r.failure;if(r.success)return c.default.createElement("div",{className:"mfa-verification-container__actions mfa-action-list"});var s=t._t("MFAWebAuthnVerify.RETRY",R["MFAWebAuthnVerify.RETRY"]),i=c.default.createElement("button",{key:s,className:"btn mfa-action-list__item btn-primary",disabled:!1,onClick:this.handleRetry,type:"button"},s);return c.default.createElement("div",{className:"mfa-verification-container__actions mfa-action-list"},a?i:null,n)}},{key:"render",value:function(){var e=this.state,t=e.failure,n=e.success;return t||n||this.startAuth(),c.default.createElement("div",{className:"mfa-verification-container mfa-verification-container--web-authn"},this.renderDescription(),this.renderStatus(),this.renderThumbnail(),this.renderActions())}}]),t}(u.Component);S.propTypes={method:f.default.object.isRequired,publicKey:p.default,onCompleteVerification:f.default.func.isRequired,moreOptionsControl:f.default.func,errors:f.default.arrayOf(f.default.string)},S.defaultProps={errors:[]},t.default=S},"./client/src/lib/auth.js":function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.performVerification=t.performRegistration=void 0;var r=Object.assign||function(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:{},t=!0,n=null;return"https:"!==window.location.protocol?(t=!1,n=window.ss.i18n._t("WebAuthnReducer.NOT_ON_HTTPS","This method can only be used over HTTPS.")):void 0===window.AuthenticatorResponse&&(t=!1,n=window.ss.i18n._t("WebAuthnReducer.UNSUPPORTED_BROWSER","Security keys are not supported by this browser")),a({},e,t?{}:{isAvailable:t,unavailableMessage:n})}Object.defineProperty(t,"__esModule",{value:!0});var a=Object.assign||function(e){for(var t=1;tB.length&&B.push(e)}function p(e,t,n,a){var s=typeof e;"undefined"!==s&&"boolean"!==s||(e=null);var i=!1;if(null===e)i=!0;else switch(s){case"string":case"number":i=!0;break;case"object":switch(e.$$typeof){case E:case R:i=!0}}if(i)return n(a,e,""===t?"."+h(e,0):t),1;if(i=0,t=""===t?".":t+":",Array.isArray(e))for(var o=0;o"+s+""});return c.default.createElement("div",{className:"mfa-registration-container__description"},c.default.createElement("p",null,t._t("MFAWebAuthnRegister.DESCRIPTION",R["MFAWebAuthnRegister.DESCRIPTION"]),r&&c.default.createElement("a",{href:r,target:"_blank",rel:"noopener noreferrer"},a||t._t("MFAWebAuthnRegister.HELP",R["MFAWebAuthnRegister.HELP"]))),c.default.createElement("p",{dangerouslySetInnerHTML:{__html:i}}))}},{key:"renderStatus",value:function(){var e=this.props.errors,t=window,n=t.ss.i18n;switch(this.state.view){case S.READY:return c.default.createElement("div",{className:"mfa-registration-container__status status-message--empty"});case S.PROMPTING:case S.LOADING:default:return c.default.createElement("div",{className:"mfa-registration-container__status status-message--loading"},c.default.createElement(v.default,{size:"3em"}),c.default.createElement("span",{className:"status-message__description"},n._t("MFAWebAuthnRegister.WAITING",R["MFAWebAuthnRegister.WAITING"])));case S.SUCCESS:return c.default.createElement("div",{className:"mfa-registration-container__status status-message--success"},c.default.createElement("span",{className:"status-message__icon"},c.default.createElement(y.default,{size:"32px"})),c.default.createElement("span",{className:"status-message__description"},n._t("MFAWebAuthnRegister.SUCCESS",R["MFAWebAuthnRegister.SUCCESS"])));case S.FAILURE:return c.default.createElement("div",{className:"mfa-registration-container__status status-message--failure"},c.default.createElement("span",{className:"status-message__icon"},c.default.createElement(g.default,{size:"32px"})),c.default.createElement("span",{className:"status-message__description"},n._t("MFAWebAuthnRegister.FAILURE",R["MFAWebAuthnRegister.FAILURE"])));case S.ERROR:return c.default.createElement("div",{className:"mfa-registration-container__status status-message--error"},c.default.createElement("span",{className:"status-message__icon"},c.default.createElement(g.default,{size:"32px"})),c.default.createElement("span",{className:"status-message__description"},e.join(", ")))}}},{key:"renderThumbnail",value:function(){return c.default.createElement("div",{className:"mfa-registration-container__thumbnail"},c.default.createElement(E.default,null))}},{key:"renderActions",value:function(){var e=window,t=e.ss.i18n,n=this.state.view,r=[];switch(n){case S.FAILURE:r=[{action:this.handleStartRegistration,name:t._t("MFAWebAuthnRegister.RETRY",R["MFAWebAuthnRegister.RETRY"])},{action:this.handleBack,name:t._t("MFAWebAuthnRegister.BACK",R["MFAWebAuthnRegister.BACK"])}];break;case S.ERROR:r=[];break;case S.READY:r=[{action:this.handleStartRegistration,name:t._t("MFAWebAuthnRegister.REGISTER",R["MFAWebAuthnRegister.REGISTER"])},{action:this.handleBack,name:t._t("MFAWebAuthnRegister.BACK",R["MFAWebAuthnRegister.BACK"])}];break;case S.PROMPTING:r=[{action:this.handleStartRegistration,name:t._t("MFAWebAuthnRegister.REGISTERING",R["MFAWebAuthnRegister.REGISTERING"]),disabled:!0},{action:this.handleBack,name:t._t("MFAWebAuthnRegister.BACK",R["MFAWebAuthnRegister.BACK"]),disabled:!0}];break;case S.LOADING:default:r=[{action:this.handleStartRegistration,name:t._t("MFAWebAuthnRegister.REGISTERING",R["MFAWebAuthnRegister.REGISTERING"]),disabled:!0},{action:this.handleBack,name:t._t("MFAWebAuthnRegister.BACK",R["MFAWebAuthnRegister.BACK"])}];break;case S.SUCCESS:r=[{action:this.handleNext,name:t._t("MFAWebAuthnRegister.COMPLETEREGISTRATION",R["MFAWebAuthnRegister.COMPLETEREGISTRATION"])}]}return c.default.createElement("div",{className:"mfa-registration-container__actions mfa-action-list"},r.map(function(e,t){var n=0===t,r=(0,m.default)("btn","mfa-action-list__item",{"btn-primary":n,"btn-secondary":!n});return c.default.createElement("button",{key:e.name,className:r,disabled:e.disabled||!1,onClick:e.action,type:"button"},e.name)}))}},{key:"render",value:function(){return c.default.createElement("div",{className:"mfa-registration-container mfa-registration-container--web-authn"},this.renderDescription(),this.renderStatus(),this.renderThumbnail(),this.renderActions())}}]),t}(u.Component);j.propTypes={keyData:f.default.object,method:f.default.object.isRequired,errors:f.default.arrayOf(f.default.string),onBack:f.default.func.isRequired,onCompleteRegistration:f.default.func.isRequired},j.defaultProps={errors:[]},j.displayName="WebAuthnRegister",t.Component=j,t.default=j},"./client/src/components/WebAuthn/Verify.js":function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function s(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var o=function(){function e(e,t){for(var n=0;n"+r+""});return c.default.createElement("div",{className:"mfa-verification-container__description"},c.default.createElement("p",null,t._t("MFAWebAuthnVerify.DESCRIPTION",R["MFAWebAuthnVerify.DESCRIPTION"]),n&&c.default.createElement("a",{href:n,target:"_blank",rel:"noopener noreferrer"},t._t("MFAWebAuthnVerify.HELP",R["MFAWebAuthnVerify.HELP"]))),c.default.createElement("p",{dangerouslySetInnerHTML:{__html:a}}))}},{key:"renderStatus",value:function(){var e=window,t=e.ss.i18n,n=this.props.errors,r=this.state,a=r.failure,s=r.success;return n.length?c.default.createElement("div",{className:"mfa-verification-container__status status-message--error"},c.default.createElement("span",{className:"status-message__icon"},c.default.createElement(g.default,{size:"32px"})),c.default.createElement("span",{className:"status-message__description"},n.join(", "))):s?c.default.createElement("div",{className:"mfa-verification-container__status status-message--success"},c.default.createElement("span",{className:"status-message__icon"},c.default.createElement(y.default,{size:"32px"})),c.default.createElement("span",{className:"status-message__description"},t._t("MFAWebAuthnVerify.SUCCESS",R["MFAWebAuthnVerify.SUCCESS"]))):a?c.default.createElement("div",{className:"mfa-verification-container__status status-message--failure"},c.default.createElement("span",{className:"status-message__icon"},c.default.createElement(g.default,{size:"32px"})),c.default.createElement("span",{className:"status-message__description"},t._t("MFAWebAuthnVerify.FAILURE",R["MFAWebAuthnVerify.FAILURE"]))):c.default.createElement("div",{className:"mfa-verification-container__status status-message--loading"},c.default.createElement(v.default,{size:"3em"}),c.default.createElement("span",{className:"status-message__description"},t._t("MFAWebAuthnVerify.WAITING",R["MFAWebAuthnVerify.WAITING"])))}},{key:"renderThumbnail",value:function(){return c.default.createElement("div",{className:"mfa-verification-container__thumbnail"},c.default.createElement(E.default,null))}},{key:"renderActions",value:function(){var e=window,t=e.ss.i18n,n=this.props.moreOptionsControl,r=this.state,a=r.failure;if(r.success)return c.default.createElement("div",{className:"mfa-verification-container__actions mfa-action-list"});var s=t._t("MFAWebAuthnVerify.RETRY",R["MFAWebAuthnVerify.RETRY"]),i=c.default.createElement("button",{key:s,className:"btn mfa-action-list__item btn-primary",disabled:!1,onClick:this.handleRetry,type:"button"},s);return c.default.createElement("div",{className:"mfa-verification-container__actions mfa-action-list"},a?i:null,n)}},{key:"render",value:function(){var e=this.state,t=e.failure,n=e.success;return t||n||this.startAuth(),c.default.createElement("div",{className:"mfa-verification-container mfa-verification-container--web-authn"},this.renderDescription(),this.renderStatus(),this.renderThumbnail(),this.renderActions())}}]),t}(u.Component);S.propTypes={method:f.default.object.isRequired,publicKey:p.default,onCompleteVerification:f.default.func.isRequired,moreOptionsControl:f.default.func,errors:f.default.arrayOf(f.default.string)},S.defaultProps={errors:[]},t.default=S},"./client/src/lib/auth.js":function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.performVerification=t.performRegistration=void 0;var r=Object.assign||function(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:{},t=!0,n=null;return"https:"!==window.location.protocol?(t=!1,n=window.ss.i18n._t("WebAuthnReducer.NOT_ON_HTTPS","This method can only be used over HTTPS.")):void 0===window.AuthenticatorResponse&&(t=!1,n=window.ss.i18n._t("WebAuthnReducer.UNSUPPORTED_BROWSER","Security keys are not supported by this browser")),a({},e,t?{}:{isAvailable:t,unavailableMessage:n})}Object.defineProperty(t,"__esModule",{value:!0});var a=Object.assign||function(e){for(var t=1;tB.length&&B.push(e)}function p(e,t,n,a){var s=typeof e;"undefined"!==s&&"boolean"!==s||(e=null);var i=!1;if(null===e)i=!0;else switch(s){case"string":case"number":i=!0;break;case"object":switch(e.$$typeof){case E:case R:i=!0}}if(i)return n(a,e,""===t?"."+h(e,0):t),1;if(i=0,t=""===t?".":t+":",Array.isArray(e))for(var o=0;o - Uint8Array.from(atob(string), c => c.charCodeAt(0)); +export const base64ToByteArray = string => { + // String replace because server with encode with 'web safe' base64_encode + const b = atob(string.replace(/_/g, '/').replace(/-/g, '+')); + return Uint8Array.from(b, c => c.charCodeAt(0)); +}; -export const byteArrayToBase64 = byteArray => - btoa(String.fromCharCode(...(new Uint8Array(byteArray)))); +export const byteArrayToBase64 = byteArray => { + // We specifically do not want to make the 'web safe' string replacements above + // doing so will break this functionality + const uarr = new Uint8Array(byteArray); + return btoa(String.fromCharCode(...uarr)); +}; diff --git a/client/src/lib/tests/convert-test.js b/client/src/lib/tests/convert-test.js new file mode 100644 index 0000000..d7ad911 --- /dev/null +++ b/client/src/lib/tests/convert-test.js @@ -0,0 +1,28 @@ +/* global jest, describe, it, expect, window */ + +import { base64ToByteArray, byteArrayToBase64 } from '../convert'; + +describe('convert', () => { + it('converts base64ToByteArray', () => { + const string = 'abc/def+ghi'; + const expected = new Uint8Array([105, 183, 63, 117, 231, 254, 130, 24]); + const actual = base64ToByteArray(string); + expect(expected).toEqual(actual); + }); + + it('converts base64ToByteArray when encoded web-safe', () => { + const string = 'abc_def-ghi'; + const expected = new Uint8Array([105, 183, 63, 117, 231, 254, 130, 24]); + const actual = base64ToByteArray(string); + expect(expected).toEqual(actual); + }); + + it('converts byteArrayToBase64', () => { + const byteArray = [105, 183, 63, 117, 231, 254, 130, 24]; + // this is supposed to be slightly different from string + // used in base64ToByteArray tests + const expected = 'abc/def+ghg='; + const actual = byteArrayToBase64(byteArray); + expect(expected).toEqual(actual); + }); +}); diff --git a/composer.json b/composer.json index a71b977..01b7aac 100644 --- a/composer.json +++ b/composer.json @@ -9,8 +9,9 @@ "license": "BSD-3-Clause", "require": { "php": "^7.3 || ^8.0", + "ext-bcmath": "*", "silverstripe/mfa": "^4.0", - "web-auth/webauthn-lib": "^1.0", + "web-auth/webauthn-lib": "^3.3", "guzzlehttp/psr7": "^1.6" }, "require-dev": { diff --git a/src/BaseHandlerTrait.php b/src/BaseHandlerTrait.php index 1d7b66a..3b6f6ad 100644 --- a/src/BaseHandlerTrait.php +++ b/src/BaseHandlerTrait.php @@ -21,6 +21,10 @@ trait BaseHandlerTrait { /** * @return Decoder + * + * @deprecated will be removed in 5.0 - as of v3 of webauthn-lib the Decoder is now created within both: + * AttestationObjectLoader::__construct() + * AuthenticatorAssertionResponseValidator::__construct() */ protected function getDecoder(): Decoder { @@ -48,7 +52,7 @@ protected function getAttestationObjectLoader( AttestationStatementSupportManager $attestationStatementSupportManager, Decoder $decoder ): AttestationObjectLoader { - return new AttestationObjectLoader($attestationStatementSupportManager, $decoder); + return new AttestationObjectLoader($attestationStatementSupportManager); } /** @@ -60,6 +64,6 @@ protected function getPublicKeyCredentialLoader( AttestationObjectLoader $attestationObjectLoader, Decoder $decoder ): PublicKeyCredentialLoader { - return new PublicKeyCredentialLoader($attestationObjectLoader, $decoder); + return new PublicKeyCredentialLoader($attestationObjectLoader); } } diff --git a/src/RegisterHandler.php b/src/RegisterHandler.php index 50463e5..e1c8013 100644 --- a/src/RegisterHandler.php +++ b/src/RegisterHandler.php @@ -109,6 +109,7 @@ public function register(HTTPRequest $request, StoreInterface $store): Result { $options = $this->getCredentialCreationOptions($store); $data = json_decode((string) $request->getBody(), true); + $publicKeyCredentialSource = null; try { if (empty($data['credentials'])) { @@ -134,7 +135,8 @@ public function register(HTTPRequest $request, StoreInterface $store): Result $psrRequest = ServerRequest::fromGlobals(); // Validate the webauthn response - $this->getAuthenticatorAttestationResponseValidator($attestationStatementSupportManager, $store) + $publicKeyCredentialSource = $this + ->getAuthenticatorAttestationResponseValidator($attestationStatementSupportManager, $store) ->check($response, $options, $psrRequest); } catch (Exception $e) { $this->logger->error($e->getMessage()); @@ -143,17 +145,12 @@ public function register(HTTPRequest $request, StoreInterface $store): Result $credentialRepository = $this->getCredentialRepository($store); - $source = PublicKeyCredentialSource::createFromPublicKeyCredential( - $publicKeyCredential, - $options->getUser()->getId() - ); - // Clear the repository so only one key is registered at a time // NOTE: This can be considered temporary behaviour until the UI supports managing multiple keys $credentialRepository->reset(); // Persist the "credential source" - $credentialRepository->saveCredentialSource($source); + $credentialRepository->saveCredentialSource($publicKeyCredentialSource); return Result::create()->setContext($credentialRepository->toArray()); } @@ -259,12 +256,10 @@ protected function getCredentialCreationOptions( $this->getUserEntity($store->getMember()), random_bytes(32), [new PublicKeyCredentialParameters('public-key', Algorithms::COSE_ALGORITHM_ES256)], - 40000, - [], - $this->getAuthenticatorSelectionCriteria(), - PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE, - new AuthenticationExtensionsClientInputs() + 40000 ); + $credentialOptions->setAuthenticatorSelection($this->getAuthenticatorSelectionCriteria()); + $credentialOptions->setAttestation(PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE); $store->setState(['credentialOptions' => $credentialOptions] + $state); diff --git a/src/VerifyHandler.php b/src/VerifyHandler.php index 4bd0695..76ac47a 100644 --- a/src/VerifyHandler.php +++ b/src/VerifyHandler.php @@ -5,6 +5,7 @@ namespace SilverStripe\WebAuthn; use CBOR\Decoder; +use Cose\Algorithm\Manager; use Exception; use GuzzleHttp\Psr7\ServerRequest; use InvalidArgumentException; @@ -22,6 +23,7 @@ use Webauthn\PublicKeyCredentialRequestOptions; use Webauthn\PublicKeyCredentialSource; use Webauthn\TokenBinding\TokenBindingNotSupportedHandler; +use Cose\Algorithm\Signature\ECDSA\ES256; class VerifyHandler implements VerifyHandlerInterface { @@ -160,13 +162,9 @@ protected function getCredentialRequestOptions( return $source->getPublicKeyCredentialDescriptor(); }, $validCredentials); - $options = new PublicKeyCredentialRequestOptions( - random_bytes(32), - 40000, - null, - $descriptors, - PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED - ); + $options = new PublicKeyCredentialRequestOptions(random_bytes(32), 40000); + $options->allowCredentials($descriptors); + $options->setUserVerification(PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED); // Persist the options for later $store->addState(['credentialOptions' => $options]); @@ -183,11 +181,13 @@ protected function getAuthenticatorAssertionResponseValidator( Decoder $decoder, StoreInterface $store ): AuthenticatorAssertionResponseValidator { + $manager = new Manager(); + $manager->add(new ES256()); return new AuthenticatorAssertionResponseValidator( $this->getCredentialRepository($store), - $decoder, new TokenBindingNotSupportedHandler(), - new ExtensionOutputCheckerHandler() + new ExtensionOutputCheckerHandler(), + $manager ); } } diff --git a/tests/RegisterHandlerTest.php b/tests/RegisterHandlerTest.php index 467d3e3..c8f675d 100644 --- a/tests/RegisterHandlerTest.php +++ b/tests/RegisterHandlerTest.php @@ -180,7 +180,10 @@ public function testRegister( ->setMethods(['getPublicKeyCredentialLoader', 'getAuthenticatorAttestationResponseValidator']) ->getMock(); + $publicKeyCredentialSourceMock = $this->createMock(PublicKeyCredentialSource::class); $responseValidatorMock = $this->createMock(AuthenticatorAttestationResponseValidator::class); + $responseValidatorMock->method('check')->willReturn($publicKeyCredentialSourceMock); + // Allow the data provider to customise the validation check handling if ($responseValidatorMockCallback) { $responseValidatorMockCallback($responseValidatorMock); diff --git a/tests/VerifyHandlerTest.php b/tests/VerifyHandlerTest.php index 744f305..19873cd 100644 --- a/tests/VerifyHandlerTest.php +++ b/tests/VerifyHandlerTest.php @@ -19,6 +19,7 @@ use Webauthn\AuthenticatorResponse; use Webauthn\PublicKeyCredential; use Webauthn\PublicKeyCredentialLoader; +use Webauthn\PublicKeyCredentialSource; class VerifyHandlerTest extends SapphireTest { @@ -132,7 +133,10 @@ public function testVerify( ->setMethods(['getPublicKeyCredentialLoader', 'getAuthenticatorAssertionResponseValidator']) ->getMock(); + $publicKeyCredentialSourceMock = $this->createMock(PublicKeyCredentialSource::class); $responseValidatorMock = $this->createMock(AuthenticatorAssertionResponseValidator::class); + $responseValidatorMock->method('check')->willReturn($publicKeyCredentialSourceMock); + // Allow the data provider to customise the validation check handling if ($responseValidatorMockCallback) { $responseValidatorMockCallback($responseValidatorMock);