Authentication workflow is complete.

pull/330/head
Clement Michaud 2019-01-16 23:01:34 +01:00
parent 9d7155a969
commit f61a052bf5
36 changed files with 1887 additions and 217 deletions

View File

@ -955,6 +955,14 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz",
"integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ=="
},
"@types/node-sass": {
"version": "3.10.32",
"resolved": "https://registry.npmjs.org/@types/node-sass/-/node-sass-3.10.32.tgz",
"integrity": "sha1-spbM5xRP+rd7hAkMqtTx5Lvqjgk=",
"requires": {
"@types/node": "*"
}
},
"@types/prop-types": {
"version": "15.5.8",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.5.8.tgz",
@ -1218,6 +1226,11 @@
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz",
"integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w=="
},
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"accepts": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
@ -1297,6 +1310,11 @@
"resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz",
"integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM="
},
"amdefine": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
"integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU="
},
"ansi-colors": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz",
@ -1605,6 +1623,15 @@
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
},
"are-we-there-yet": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
"integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
"requires": {
"delegates": "^1.0.0",
"readable-stream": "^2.0.6"
}
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@ -1650,6 +1677,11 @@
"resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz",
"integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw="
},
"array-find-index": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
"integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E="
},
"array-flatten": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz",
@ -1776,6 +1808,11 @@
"resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz",
"integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0="
},
"async-foreach": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz",
"integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI="
},
"async-limiter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
@ -2451,6 +2488,14 @@
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz",
"integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg=="
},
"block-stream": {
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
"integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
"requires": {
"inherits": "~2.0.0"
}
},
"bluebird": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz",
@ -2795,6 +2840,22 @@
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz",
"integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA=="
},
"camelcase-keys": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
"integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
"requires": {
"camelcase": "^2.0.0",
"map-obj": "^1.0.0"
},
"dependencies": {
"camelcase": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
"integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8="
}
}
},
"caniuse-api": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
@ -3244,6 +3305,11 @@
"date-now": "^0.1.4"
}
},
"console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
},
"constants-browserify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
@ -3804,6 +3870,14 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.0.tgz",
"integrity": "sha512-by8hi8BlLbowQq0qtkx54d9aN73R9oUW20HISpka5kmgsR9F7nnxgfsemuR2sdCKZh+CDNf5egW9UZMm4mgJRg=="
},
"currently-unhandled": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
"integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
"requires": {
"array-find-index": "^1.0.1"
}
},
"cyclist": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz",
@ -4002,6 +4076,11 @@
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@ -5878,11 +5957,13 @@
},
"balanced-match": {
"version": "1.0.0",
"bundled": true
"bundled": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -5895,15 +5976,18 @@
},
"code-point-at": {
"version": "1.1.0",
"bundled": true
"bundled": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true
"bundled": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true
"bundled": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -6006,7 +6090,8 @@
},
"inherits": {
"version": "2.0.3",
"bundled": true
"bundled": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -6016,6 +6101,7 @@
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -6028,17 +6114,20 @@
"minimatch": {
"version": "3.0.4",
"bundled": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
"bundled": true
"bundled": true,
"optional": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@ -6055,6 +6144,7 @@
"mkdirp": {
"version": "0.5.1",
"bundled": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -6127,7 +6217,8 @@
},
"number-is-nan": {
"version": "1.0.1",
"bundled": true
"bundled": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -6137,6 +6228,7 @@
"once": {
"version": "1.4.0",
"bundled": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -6242,6 +6334,7 @@
"string-width": {
"version": "1.0.2",
"bundled": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -6305,6 +6398,17 @@
}
}
},
"fstream": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
"integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
"requires": {
"graceful-fs": "^4.1.2",
"inherits": "~2.0.0",
"mkdirp": ">=0.5 0",
"rimraf": "2"
}
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
@ -6315,6 +6419,49 @@
"resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
"integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc="
},
"gauge": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
"requires": {
"aproba": "^1.0.3",
"console-control-strings": "^1.0.0",
"has-unicode": "^2.0.0",
"object-assign": "^4.1.0",
"signal-exit": "^3.0.0",
"string-width": "^1.0.1",
"strip-ansi": "^3.0.1",
"wide-align": "^1.1.0"
},
"dependencies": {
"is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"requires": {
"number-is-nan": "^1.0.0"
}
},
"string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
"strip-ansi": "^3.0.0"
}
}
}
},
"gaze": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz",
"integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==",
"requires": {
"globule": "^1.0.0"
}
},
"get-caller-file": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
@ -6325,6 +6472,11 @@
"resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz",
"integrity": "sha512-CIJYJC4GGF06TakLg8z4GQKvDsx9EMspVxOYih7LerEL/WosUnFIww45CGfxfeKHqlg3twgUrYRT1O3WQqjGCg=="
},
"get-stdin": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
"integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4="
},
"get-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
@ -6431,6 +6583,16 @@
}
}
},
"globule": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz",
"integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==",
"requires": {
"glob": "~7.1.1",
"lodash": "~4.17.10",
"minimatch": "~3.0.2"
}
},
"graceful-fs": {
"version": "4.1.15",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
@ -6573,6 +6735,11 @@
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
"integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q="
},
"has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
},
"has-value": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
@ -7252,6 +7419,11 @@
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
},
"in-publish": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz",
"integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E="
},
"indefinite-observable": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/indefinite-observable/-/indefinite-observable-1.0.2.tgz",
@ -7260,6 +7432,14 @@
"symbol-observable": "1.2.0"
}
},
"indent-string": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
"integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
"requires": {
"repeating": "^2.0.0"
}
},
"indexes-of": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
@ -8327,6 +8507,11 @@
"topo": "2.x.x"
}
},
"js-base64": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.0.tgz",
"integrity": "sha512-wlEBIZ5LP8usDylWbDNhKPEFVFdI5hCHpnVoT/Ysvoi/PRhJENm/Rlh9TvjYB38HFfKZN7OzEbRjmjvLkFw11g=="
},
"js-levenshtein": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.5.tgz",
@ -8712,11 +8897,21 @@
"resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
"integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0="
},
"lodash.assign": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
"integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc="
},
"lodash.camelcase": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY="
},
"lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
},
"lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@ -8727,6 +8922,11 @@
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4="
},
"lodash.mergewith": {
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz",
"integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ=="
},
"lodash.sortby": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
@ -8772,6 +8972,15 @@
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
"loud-rejection": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
"integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
"requires": {
"currently-unhandled": "^0.4.1",
"signal-exit": "^3.0.0"
}
},
"lower-case": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
@ -8827,6 +9036,11 @@
"resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
"integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8="
},
"map-obj": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
"integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0="
},
"map-visit": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
@ -8877,6 +9091,23 @@
"readable-stream": "^2.0.1"
}
},
"meow": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
"integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
"requires": {
"camelcase-keys": "^2.0.0",
"decamelize": "^1.1.2",
"loud-rejection": "^1.0.0",
"map-obj": "^1.0.1",
"minimist": "^1.1.3",
"normalize-package-data": "^2.3.4",
"object-assign": "^4.0.1",
"read-pkg-up": "^1.0.1",
"redent": "^1.0.0",
"trim-newlines": "^1.0.0"
}
},
"merge": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz",
@ -9107,8 +9338,7 @@
"nan": {
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz",
"integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==",
"optional": true
"integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw=="
},
"nanomatch": {
"version": "1.2.13",
@ -9187,6 +9417,32 @@
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz",
"integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ=="
},
"node-gyp": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",
"integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==",
"requires": {
"fstream": "^1.0.0",
"glob": "^7.0.3",
"graceful-fs": "^4.1.2",
"mkdirp": "^0.5.0",
"nopt": "2 || 3",
"npmlog": "0 || 1 || 2 || 3 || 4",
"osenv": "0",
"request": "^2.87.0",
"rimraf": "2",
"semver": "~5.3.0",
"tar": "^2.0.0",
"which": "1"
},
"dependencies": {
"semver": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8="
}
}
},
"node-int64": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
@ -9248,6 +9504,73 @@
"semver": "^5.3.0"
}
},
"node-sass": {
"version": "4.11.0",
"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.11.0.tgz",
"integrity": "sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA==",
"requires": {
"async-foreach": "^0.1.3",
"chalk": "^1.1.1",
"cross-spawn": "^3.0.0",
"gaze": "^1.0.0",
"get-stdin": "^4.0.1",
"glob": "^7.0.3",
"in-publish": "^2.0.0",
"lodash.assign": "^4.2.0",
"lodash.clonedeep": "^4.3.2",
"lodash.mergewith": "^4.6.0",
"meow": "^3.7.0",
"mkdirp": "^0.5.1",
"nan": "^2.10.0",
"node-gyp": "^3.8.0",
"npmlog": "^4.0.0",
"request": "^2.88.0",
"sass-graph": "^2.2.4",
"stdout-stream": "^1.4.0",
"true-case-path": "^1.0.2"
},
"dependencies": {
"ansi-styles": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"requires": {
"ansi-styles": "^2.2.1",
"escape-string-regexp": "^1.0.2",
"has-ansi": "^2.0.0",
"strip-ansi": "^3.0.0",
"supports-color": "^2.0.0"
}
},
"cross-spawn": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
"integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=",
"requires": {
"lru-cache": "^4.0.1",
"which": "^1.2.9"
}
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
}
}
},
"nopt": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
"integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
"requires": {
"abbrev": "1"
}
},
"normalize-package-data": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
@ -9290,6 +9613,17 @@
"path-key": "^2.0.0"
}
},
"npmlog": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"requires": {
"are-we-there-yet": "~1.1.2",
"console-control-strings": "~1.1.0",
"gauge": "~2.7.3",
"set-blocking": "~2.0.0"
}
},
"nth-check": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
@ -9527,6 +9861,15 @@
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
},
"osenv": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
"integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
"requires": {
"os-homedir": "^1.0.0",
"os-tmpdir": "^1.0.0"
}
},
"p-defer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
@ -14197,6 +14540,15 @@
"minimatch": "3.0.4"
}
},
"redent": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
"integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
"requires": {
"indent-string": "^2.1.0",
"strip-indent": "^1.0.1"
}
},
"redux": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.0.1.tgz",
@ -14866,6 +15218,93 @@
}
}
},
"sass-graph": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz",
"integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=",
"requires": {
"glob": "^7.0.0",
"lodash": "^4.0.0",
"scss-tokenizer": "^0.2.3",
"yargs": "^7.0.0"
},
"dependencies": {
"camelcase": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
"integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo="
},
"cliui": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
"integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
"requires": {
"string-width": "^1.0.1",
"strip-ansi": "^3.0.1",
"wrap-ansi": "^2.0.0"
}
},
"is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"requires": {
"number-is-nan": "^1.0.0"
}
},
"os-locale": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
"integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
"requires": {
"lcid": "^1.0.0"
}
},
"string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
"strip-ansi": "^3.0.0"
}
},
"which-module": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz",
"integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8="
},
"yargs": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz",
"integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=",
"requires": {
"camelcase": "^3.0.0",
"cliui": "^3.2.0",
"decamelize": "^1.1.1",
"get-caller-file": "^1.0.1",
"os-locale": "^1.4.0",
"read-pkg-up": "^1.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^1.0.1",
"set-blocking": "^2.0.0",
"string-width": "^1.0.2",
"which-module": "^1.0.0",
"y18n": "^3.2.1",
"yargs-parser": "^5.0.0"
}
},
"yargs-parser": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz",
"integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=",
"requires": {
"camelcase": "^3.0.0"
}
}
}
},
"sass-loader": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.1.0.tgz",
@ -14959,6 +15398,25 @@
"ajv-keywords": "^3.1.0"
}
},
"scss-tokenizer": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
"integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=",
"requires": {
"js-base64": "^2.1.8",
"source-map": "^0.4.2"
},
"dependencies": {
"source-map": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
"integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
"requires": {
"amdefine": ">=0.0.4"
}
}
}
},
"select-hose": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
@ -15523,6 +15981,14 @@
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
},
"stdout-stream": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz",
"integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==",
"requires": {
"readable-stream": "^2.0.1"
}
},
"stealthy-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
@ -15664,6 +16130,14 @@
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
},
"strip-indent": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
"integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
"requires": {
"get-stdin": "^4.0.1"
}
},
"strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
@ -15811,6 +16285,16 @@
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.1.tgz",
"integrity": "sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA=="
},
"tar": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
"integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
"requires": {
"block-stream": "*",
"fstream": "^1.0.2",
"inherits": "2"
}
},
"terser": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-3.14.1.tgz",
@ -16060,11 +16544,24 @@
"punycode": "^2.1.0"
}
},
"trim-newlines": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
"integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM="
},
"trim-right": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
"integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM="
},
"true-case-path": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz",
"integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==",
"requires": {
"glob": "^7.1.2"
}
},
"tryer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz",
@ -17237,6 +17734,14 @@
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
},
"wide-align": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
"requires": {
"string-width": "^1.0.2 || 2"
}
},
"wordwrap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",

View File

@ -8,6 +8,7 @@
"@types/classnames": "^2.2.7",
"@types/jss": "^9.5.7",
"@types/node": "^10.12.2",
"@types/node-sass": "^3.10.32",
"@types/qrcode.react": "^0.8.1",
"@types/query-string": "^6.2.0",
"@types/react": "^16.4.18",
@ -18,6 +19,7 @@
"await-to-js": "^2.1.1",
"classnames": "^2.2.6",
"jss": "^9.8.7",
"node-sass": "^4.11.0",
"qrcode.react": "^0.9.2",
"query-string": "^6.2.0",
"react": "^16.6.0",

View File

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="US_UK_Download_on_the" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px" width="135px" height="40px" viewBox="0 0 135 40" enable-background="new 0 0 135 40" xml:space="preserve">
<g>
<path fill="#A6A6A6" d="M130.197,40H4.729C2.122,40,0,37.872,0,35.267V4.726C0,2.12,2.122,0,4.729,0h125.468
C132.803,0,135,2.12,135,4.726v30.541C135,37.872,132.803,40,130.197,40L130.197,40z"/>
<path d="M134.032,35.268c0,2.116-1.714,3.83-3.834,3.83H4.729c-2.119,0-3.839-1.714-3.839-3.83V4.725
c0-2.115,1.72-3.835,3.839-3.835h125.468c2.121,0,3.834,1.72,3.834,3.835L134.032,35.268L134.032,35.268z"/>
<g>
<g>
<path fill="#FFFFFF" d="M30.128,19.784c-0.029-3.223,2.639-4.791,2.761-4.864c-1.511-2.203-3.853-2.504-4.676-2.528
c-1.967-0.207-3.875,1.177-4.877,1.177c-1.022,0-2.565-1.157-4.228-1.123c-2.14,0.033-4.142,1.272-5.24,3.196
c-2.266,3.923-0.576,9.688,1.595,12.859c1.086,1.553,2.355,3.287,4.016,3.226c1.625-0.067,2.232-1.036,4.193-1.036
c1.943,0,2.513,1.036,4.207,0.997c1.744-0.028,2.842-1.56,3.89-3.127c1.255-1.78,1.759-3.533,1.779-3.623
C33.507,24.924,30.161,23.647,30.128,19.784z"/>
<path fill="#FFFFFF" d="M26.928,10.306c0.874-1.093,1.472-2.58,1.306-4.089c-1.265,0.056-2.847,0.875-3.758,1.944
c-0.806,0.942-1.526,2.486-1.34,3.938C24.557,12.205,26.016,11.382,26.928,10.306z"/>
</g>
</g>
<g>
<path fill="#FFFFFF" d="M53.645,31.504h-2.271l-1.244-3.909h-4.324l-1.185,3.909h-2.211l4.284-13.308h2.646L53.645,31.504z
M49.755,25.955L48.63,22.48c-0.119-0.355-0.342-1.191-0.671-2.507h-0.04c-0.131,0.566-0.342,1.402-0.632,2.507l-1.105,3.475
H49.755z"/>
<path fill="#FFFFFF" d="M64.662,26.588c0,1.632-0.441,2.922-1.323,3.869c-0.79,0.843-1.771,1.264-2.942,1.264
c-1.264,0-2.172-0.454-2.725-1.362h-0.04v5.055h-2.132V25.067c0-1.026-0.027-2.079-0.079-3.159h1.875l0.119,1.521h0.04
c0.711-1.146,1.79-1.718,3.238-1.718c1.132,0,2.077,0.447,2.833,1.342C64.284,23.949,64.662,25.127,64.662,26.588z M62.49,26.666
c0-0.934-0.21-1.704-0.632-2.31c-0.461-0.632-1.08-0.948-1.856-0.948c-0.526,0-1.004,0.176-1.431,0.523
c-0.428,0.35-0.708,0.807-0.839,1.373c-0.066,0.264-0.099,0.48-0.099,0.65v1.6c0,0.698,0.214,1.287,0.642,1.768
s0.984,0.721,1.668,0.721c0.803,0,1.428-0.31,1.875-0.928C62.266,28.496,62.49,27.68,62.49,26.666z"/>
<path fill="#FFFFFF" d="M75.699,26.588c0,1.632-0.441,2.922-1.324,3.869c-0.789,0.843-1.77,1.264-2.941,1.264
c-1.264,0-2.172-0.454-2.724-1.362H68.67v5.055h-2.132V25.067c0-1.026-0.027-2.079-0.079-3.159h1.875l0.119,1.521h0.04
c0.71-1.146,1.789-1.718,3.238-1.718c1.131,0,2.076,0.447,2.834,1.342C75.32,23.949,75.699,25.127,75.699,26.588z M73.527,26.666
c0-0.934-0.211-1.704-0.633-2.31c-0.461-0.632-1.078-0.948-1.855-0.948c-0.527,0-1.004,0.176-1.432,0.523
c-0.428,0.35-0.707,0.807-0.838,1.373c-0.065,0.264-0.099,0.48-0.099,0.65v1.6c0,0.698,0.214,1.287,0.64,1.768
c0.428,0.48,0.984,0.721,1.67,0.721c0.803,0,1.428-0.31,1.875-0.928C73.303,28.496,73.527,27.68,73.527,26.666z"/>
<path fill="#FFFFFF" d="M88.039,27.772c0,1.132-0.393,2.053-1.182,2.764c-0.867,0.777-2.074,1.165-3.625,1.165
c-1.432,0-2.58-0.276-3.449-0.829l0.494-1.777c0.936,0.566,1.963,0.85,3.082,0.85c0.803,0,1.428-0.182,1.877-0.544
c0.447-0.362,0.67-0.848,0.67-1.454c0-0.54-0.184-0.995-0.553-1.364c-0.367-0.369-0.98-0.712-1.836-1.029
c-2.33-0.869-3.494-2.142-3.494-3.816c0-1.094,0.408-1.991,1.225-2.689c0.814-0.699,1.9-1.048,3.258-1.048
c1.211,0,2.217,0.211,3.02,0.632l-0.533,1.738c-0.75-0.408-1.598-0.612-2.547-0.612c-0.75,0-1.336,0.185-1.756,0.553
c-0.355,0.329-0.533,0.73-0.533,1.205c0,0.526,0.203,0.961,0.611,1.303c0.355,0.316,1,0.658,1.936,1.027
c1.145,0.461,1.986,1,2.527,1.618C87.77,26.081,88.039,26.852,88.039,27.772z"/>
<path fill="#FFFFFF" d="M95.088,23.508h-2.35v4.659c0,1.185,0.414,1.777,1.244,1.777c0.381,0,0.697-0.033,0.947-0.099l0.059,1.619
c-0.42,0.157-0.973,0.236-1.658,0.236c-0.842,0-1.5-0.257-1.975-0.77c-0.473-0.514-0.711-1.376-0.711-2.587v-4.837h-1.4v-1.6h1.4
v-1.757l2.094-0.632v2.389h2.35V23.508z"/>
<path fill="#FFFFFF" d="M105.691,26.627c0,1.475-0.422,2.686-1.264,3.633c-0.883,0.975-2.055,1.461-3.516,1.461
c-1.408,0-2.529-0.467-3.365-1.401s-1.254-2.113-1.254-3.534c0-1.487,0.43-2.705,1.293-3.652c0.861-0.948,2.023-1.422,3.484-1.422
c1.408,0,2.541,0.467,3.396,1.402C105.283,24.021,105.691,25.192,105.691,26.627z M103.479,26.696
c0-0.885-0.189-1.644-0.572-2.277c-0.447-0.766-1.086-1.148-1.914-1.148c-0.857,0-1.508,0.383-1.955,1.148
c-0.383,0.634-0.572,1.405-0.572,2.317c0,0.885,0.189,1.644,0.572,2.276c0.461,0.766,1.105,1.148,1.936,1.148
c0.814,0,1.453-0.39,1.914-1.168C103.281,28.347,103.479,27.58,103.479,26.696z"/>
<path fill="#FFFFFF" d="M112.621,23.783c-0.211-0.039-0.436-0.059-0.672-0.059c-0.75,0-1.33,0.283-1.738,0.85
c-0.355,0.5-0.533,1.132-0.533,1.895v5.035h-2.131l0.02-6.574c0-1.106-0.027-2.113-0.08-3.021h1.857l0.078,1.836h0.059
c0.225-0.631,0.58-1.139,1.066-1.52c0.475-0.343,0.988-0.514,1.541-0.514c0.197,0,0.375,0.014,0.533,0.039V23.783z"/>
<path fill="#FFFFFF" d="M122.156,26.252c0,0.382-0.025,0.704-0.078,0.967h-6.396c0.025,0.948,0.334,1.673,0.928,2.173
c0.539,0.447,1.236,0.671,2.092,0.671c0.947,0,1.811-0.151,2.588-0.454l0.334,1.48c-0.908,0.396-1.98,0.593-3.217,0.593
c-1.488,0-2.656-0.438-3.506-1.313c-0.848-0.875-1.273-2.05-1.273-3.524c0-1.447,0.395-2.652,1.186-3.613
c0.828-1.026,1.947-1.539,3.355-1.539c1.383,0,2.43,0.513,3.141,1.539C121.873,24.047,122.156,25.055,122.156,26.252z
M120.123,25.699c0.014-0.632-0.125-1.178-0.414-1.639c-0.369-0.593-0.936-0.889-1.699-0.889c-0.697,0-1.264,0.289-1.697,0.869
c-0.355,0.461-0.566,1.014-0.631,1.658H120.123z"/>
</g>
<g>
<g>
<path fill="#FFFFFF" d="M49.05,10.009c0,1.177-0.353,2.063-1.058,2.658c-0.653,0.549-1.581,0.824-2.783,0.824
c-0.596,0-1.106-0.026-1.533-0.078V6.982c0.557-0.09,1.157-0.136,1.805-0.136c1.145,0,2.008,0.249,2.59,0.747
C48.723,8.156,49.05,8.961,49.05,10.009z M47.945,10.038c0-0.763-0.202-1.348-0.606-1.756c-0.404-0.407-0.994-0.611-1.771-0.611
c-0.33,0-0.611,0.022-0.844,0.068v4.889c0.129,0.02,0.365,0.029,0.708,0.029c0.802,0,1.421-0.223,1.857-0.669
S47.945,10.892,47.945,10.038z"/>
<path fill="#FFFFFF" d="M54.909,11.037c0,0.725-0.207,1.319-0.621,1.785c-0.434,0.479-1.009,0.718-1.727,0.718
c-0.692,0-1.243-0.229-1.654-0.689c-0.41-0.459-0.615-1.038-0.615-1.736c0-0.73,0.211-1.329,0.635-1.794s0.994-0.698,1.712-0.698
c0.692,0,1.248,0.229,1.669,0.688C54.708,9.757,54.909,10.333,54.909,11.037z M53.822,11.071c0-0.435-0.094-0.808-0.281-1.119
c-0.22-0.376-0.533-0.564-0.94-0.564c-0.421,0-0.741,0.188-0.961,0.564c-0.188,0.311-0.281,0.69-0.281,1.138
c0,0.435,0.094,0.808,0.281,1.119c0.227,0.376,0.543,0.564,0.951,0.564c0.4,0,0.714-0.191,0.94-0.574
C53.725,11.882,53.822,11.506,53.822,11.071z"/>
<path fill="#FFFFFF" d="M62.765,8.719l-1.475,4.714h-0.96l-0.611-2.047c-0.155-0.511-0.281-1.019-0.379-1.523h-0.019
c-0.091,0.518-0.217,1.025-0.379,1.523l-0.649,2.047h-0.971l-1.387-4.714h1.077l0.533,2.241c0.129,0.53,0.235,1.035,0.32,1.513
h0.019c0.078-0.394,0.207-0.896,0.389-1.503l0.669-2.25h0.854l0.641,2.202c0.155,0.537,0.281,1.054,0.378,1.552h0.029
c0.071-0.485,0.178-1.002,0.32-1.552l0.572-2.202H62.765z"/>
<path fill="#FFFFFF" d="M68.198,13.433H67.15v-2.7c0-0.832-0.316-1.248-0.95-1.248c-0.311,0-0.562,0.114-0.757,0.343
c-0.193,0.229-0.291,0.499-0.291,0.808v2.796h-1.048v-3.366c0-0.414-0.013-0.863-0.038-1.349h0.921l0.049,0.737h0.029
c0.122-0.229,0.304-0.418,0.543-0.569c0.284-0.176,0.602-0.265,0.95-0.265c0.44,0,0.806,0.142,1.097,0.427
c0.362,0.349,0.543,0.87,0.543,1.562V13.433z"/>
<path fill="#FFFFFF" d="M71.088,13.433h-1.047V6.556h1.047V13.433z"/>
<path fill="#FFFFFF" d="M77.258,11.037c0,0.725-0.207,1.319-0.621,1.785c-0.434,0.479-1.01,0.718-1.727,0.718
c-0.693,0-1.244-0.229-1.654-0.689c-0.41-0.459-0.615-1.038-0.615-1.736c0-0.73,0.211-1.329,0.635-1.794s0.994-0.698,1.711-0.698
c0.693,0,1.248,0.229,1.67,0.688C77.057,9.757,77.258,10.333,77.258,11.037z M76.17,11.071c0-0.435-0.094-0.808-0.281-1.119
c-0.219-0.376-0.533-0.564-0.939-0.564c-0.422,0-0.742,0.188-0.961,0.564c-0.188,0.311-0.281,0.69-0.281,1.138
c0,0.435,0.094,0.808,0.281,1.119c0.227,0.376,0.543,0.564,0.951,0.564c0.4,0,0.713-0.191,0.939-0.574
C76.074,11.882,76.17,11.506,76.17,11.071z"/>
<path fill="#FFFFFF" d="M82.33,13.433h-0.941l-0.078-0.543h-0.029c-0.322,0.433-0.781,0.65-1.377,0.65
c-0.445,0-0.805-0.143-1.076-0.427c-0.246-0.258-0.369-0.579-0.369-0.96c0-0.576,0.24-1.015,0.723-1.319
c0.482-0.304,1.16-0.453,2.033-0.446V10.3c0-0.621-0.326-0.931-0.979-0.931c-0.465,0-0.875,0.117-1.229,0.349l-0.213-0.688
c0.438-0.271,0.979-0.407,1.617-0.407c1.232,0,1.85,0.65,1.85,1.95v1.736C82.262,12.78,82.285,13.155,82.33,13.433z
M81.242,11.813v-0.727c-1.156-0.02-1.734,0.297-1.734,0.95c0,0.246,0.066,0.43,0.201,0.553c0.135,0.123,0.307,0.184,0.512,0.184
c0.23,0,0.445-0.073,0.641-0.218c0.197-0.146,0.318-0.331,0.363-0.558C81.236,11.946,81.242,11.884,81.242,11.813z"/>
<path fill="#FFFFFF" d="M88.285,13.433h-0.93l-0.049-0.757h-0.029c-0.297,0.576-0.803,0.864-1.514,0.864
c-0.568,0-1.041-0.223-1.416-0.669s-0.562-1.025-0.562-1.736c0-0.763,0.203-1.381,0.611-1.853c0.395-0.44,0.879-0.66,1.455-0.66
c0.633,0,1.076,0.213,1.328,0.64h0.02V6.556h1.049v5.607C88.248,12.622,88.26,13.045,88.285,13.433z M87.199,11.445v-0.786
c0-0.136-0.01-0.246-0.029-0.33c-0.059-0.252-0.186-0.464-0.379-0.635c-0.195-0.171-0.43-0.257-0.701-0.257
c-0.391,0-0.697,0.155-0.922,0.466c-0.223,0.311-0.336,0.708-0.336,1.193c0,0.466,0.107,0.844,0.322,1.135
c0.227,0.31,0.533,0.465,0.916,0.465c0.344,0,0.619-0.129,0.828-0.388C87.1,12.069,87.199,11.781,87.199,11.445z"/>
<path fill="#FFFFFF" d="M97.248,11.037c0,0.725-0.207,1.319-0.621,1.785c-0.434,0.479-1.008,0.718-1.727,0.718
c-0.691,0-1.242-0.229-1.654-0.689c-0.41-0.459-0.615-1.038-0.615-1.736c0-0.73,0.211-1.329,0.635-1.794s0.994-0.698,1.713-0.698
c0.691,0,1.248,0.229,1.668,0.688C97.047,9.757,97.248,10.333,97.248,11.037z M96.162,11.071c0-0.435-0.094-0.808-0.281-1.119
c-0.221-0.376-0.533-0.564-0.941-0.564c-0.42,0-0.74,0.188-0.961,0.564c-0.188,0.311-0.281,0.69-0.281,1.138
c0,0.435,0.094,0.808,0.281,1.119c0.227,0.376,0.543,0.564,0.951,0.564c0.4,0,0.715-0.191,0.941-0.574
C96.064,11.882,96.162,11.506,96.162,11.071z"/>
<path fill="#FFFFFF" d="M102.883,13.433h-1.047v-2.7c0-0.832-0.316-1.248-0.951-1.248c-0.311,0-0.562,0.114-0.756,0.343
s-0.291,0.499-0.291,0.808v2.796h-1.049v-3.366c0-0.414-0.012-0.863-0.037-1.349h0.92l0.049,0.737h0.029
c0.123-0.229,0.305-0.418,0.543-0.569c0.285-0.176,0.602-0.265,0.951-0.265c0.439,0,0.805,0.142,1.096,0.427
c0.363,0.349,0.543,0.87,0.543,1.562V13.433z"/>
<path fill="#FFFFFF" d="M109.936,9.504h-1.154v2.29c0,0.582,0.205,0.873,0.611,0.873c0.188,0,0.344-0.016,0.467-0.049
l0.027,0.795c-0.207,0.078-0.479,0.117-0.814,0.117c-0.414,0-0.736-0.126-0.969-0.378c-0.234-0.252-0.35-0.676-0.35-1.271V9.504
h-0.689V8.719h0.689V7.855l1.027-0.31v1.173h1.154V9.504z"/>
<path fill="#FFFFFF" d="M115.484,13.433h-1.049v-2.68c0-0.845-0.316-1.268-0.949-1.268c-0.486,0-0.818,0.245-1,0.735
c-0.031,0.103-0.049,0.229-0.049,0.377v2.835h-1.047V6.556h1.047v2.841h0.02c0.33-0.517,0.803-0.775,1.416-0.775
c0.434,0,0.793,0.142,1.078,0.427c0.355,0.355,0.533,0.883,0.533,1.581V13.433z"/>
<path fill="#FFFFFF" d="M121.207,10.853c0,0.188-0.014,0.346-0.039,0.475h-3.143c0.014,0.466,0.164,0.821,0.455,1.067
c0.266,0.22,0.609,0.33,1.029,0.33c0.465,0,0.889-0.074,1.271-0.223l0.164,0.728c-0.447,0.194-0.973,0.291-1.582,0.291
c-0.73,0-1.305-0.215-1.721-0.645c-0.418-0.43-0.625-1.007-0.625-1.731c0-0.711,0.193-1.303,0.582-1.775
c0.406-0.504,0.955-0.756,1.648-0.756c0.678,0,1.193,0.252,1.541,0.756C121.068,9.77,121.207,10.265,121.207,10.853z
M120.207,10.582c0.008-0.311-0.061-0.579-0.203-0.805c-0.182-0.291-0.459-0.437-0.834-0.437c-0.342,0-0.621,0.142-0.834,0.427
c-0.174,0.227-0.277,0.498-0.311,0.815H120.207z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,429 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
xml:space="preserve"
width="135.71649"
height="40.018951"
viewBox="0 0 135.71649 40.018951"
sodipodi:docname="google-play-badge.svg"><metadata
id="metadata8"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs6"><linearGradient
x1="31.7997"
y1="183.2903"
x2="15.0173"
y2="166.5079"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.8,0,0,-0.8,0,161.6)"
spreadMethod="pad"
id="linearGradient50"><stop
style="stop-opacity:1;stop-color:#00a0ff"
offset="0"
id="stop52" /><stop
style="stop-opacity:1;stop-color:#00a1ff"
offset="0.0066"
id="stop54" /><stop
style="stop-opacity:1;stop-color:#00beff"
offset="0.2601"
id="stop56" /><stop
style="stop-opacity:1;stop-color:#00d2ff"
offset="0.5122"
id="stop58" /><stop
style="stop-opacity:1;stop-color:#00dfff"
offset="0.7604"
id="stop60" /><stop
style="stop-opacity:1;stop-color:#00e3ff"
offset="1"
id="stop62" /></linearGradient><linearGradient
x1="43.8344"
y1="171.9986"
x2="19.637501"
y2="171.9986"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.8,0,0,-0.8,0,161.6)"
spreadMethod="pad"
id="linearGradient68"><stop
style="stop-opacity:1;stop-color:#ffe000"
offset="0"
id="stop70" /><stop
style="stop-opacity:1;stop-color:#ffbd00"
offset="0.4087"
id="stop72" /><stop
style="stop-opacity:1;stop-color:#ffa500"
offset="0.7754"
id="stop74" /><stop
style="stop-opacity:1;stop-color:#ff9c00"
offset="1"
id="stop76" /></linearGradient><linearGradient
x1="34.827"
y1="169.7039"
x2="12.0687"
y2="146.9456"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.8,0,0,-0.8,0,161.6)"
spreadMethod="pad"
id="linearGradient82"><stop
style="stop-opacity:1;stop-color:#ff3a44"
offset="0"
id="stop84" /><stop
style="stop-opacity:1;stop-color:#c31162"
offset="1"
id="stop86" /></linearGradient><linearGradient
x1="17.2973"
y1="191.82381"
x2="27.4599"
y2="181.6613"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.8,0,0,-0.8,0,161.6)"
spreadMethod="pad"
id="linearGradient92"><stop
style="stop-opacity:1;stop-color:#32a071"
offset="0"
id="stop94" /><stop
style="stop-opacity:1;stop-color:#2da771"
offset="0.0685"
id="stop96" /><stop
style="stop-opacity:1;stop-color:#15cf74"
offset="0.4762"
id="stop98" /><stop
style="stop-opacity:1;stop-color:#06e775"
offset="0.8009"
id="stop100" /><stop
style="stop-opacity:1;stop-color:#00f076"
offset="1"
id="stop102" /></linearGradient><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath110"><path
d="M 0,0 124,0 124,48 0,48 0,0 Z"
id="path112"
inkscape:connector-curvature="0" /></clipPath><mask
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="1"
height="1"
id="mask114"><g
id="g116"><g
clip-path="url(#clipPath110)"
id="g118"><path
d="M 0,0 124,0 124,48 0,48 0,0 Z"
style="fill:#000000;fill-opacity:0.2;fill-rule:nonzero;stroke:none"
id="path120"
inkscape:connector-curvature="0" /></g></g></mask><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath126"><path
d="M 0,0 124,0 124,48 0,48 0,0 Z"
id="path128"
inkscape:connector-curvature="0" /></clipPath><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath130"><path
d="M 0,0 124,0 124,48 0,48 0,0 Z"
id="path132"
inkscape:connector-curvature="0" /></clipPath><pattern
patternTransform="matrix(1,0,0,-1,0,48)"
patternUnits="userSpaceOnUse"
x="0"
y="0"
width="124"
height="48"
id="pattern134"><g
id="g136" /><g
id="g138"><g
clip-path="url(#clipPath130)"
id="g140"><g
id="g142"><path
d="M 29.625,20.695 18.012,14.098 C 17.363,13.727 16.781,13.754 16.406,14.09 l -0.058,-0.063 0.058,-0.058 c 0.375,-0.336 0.957,-0.36 1.606,0.011 l 11.687,6.641 -0.074,0.074 z"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path144" /></g></g></g></pattern><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath158"><path
d="M 0,0 124,0 124,48 0,48 0,0 Z"
id="path160"
inkscape:connector-curvature="0" /></clipPath><mask
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="1"
height="1"
id="mask162"><g
id="g164"><g
clip-path="url(#clipPath158)"
id="g166"><path
d="M 0,0 124,0 124,48 0,48 0,0 Z"
style="fill:#000000;fill-opacity:0.12000002;fill-rule:nonzero;stroke:none"
id="path168"
inkscape:connector-curvature="0" /></g></g></mask><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath174"><path
d="M 0,0 124,0 124,48 0,48 0,0 Z"
id="path176"
inkscape:connector-curvature="0" /></clipPath><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath178"><path
d="M 0,0 124,0 124,48 0,48 0,0 Z"
id="path180"
inkscape:connector-curvature="0" /></clipPath><pattern
patternTransform="matrix(1,0,0,-1,0,48)"
patternUnits="userSpaceOnUse"
x="0"
y="0"
width="124"
height="48"
id="pattern182"><g
id="g184" /><g
id="g186"><g
clip-path="url(#clipPath178)"
id="g188"><g
id="g190"><path
d="m 16.348,14.145 c -0.235,0.246 -0.371,0.628 -0.371,1.125 l 0,-0.118 c 0,-0.496 0.136,-0.879 0.371,-1.125 l 0.058,0.063 -0.058,0.055 z"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path192" /></g></g></g></pattern><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath206"><path
d="M 0,0 124,0 124,48 0,48 0,0 Z"
id="path208"
inkscape:connector-curvature="0" /></clipPath><mask
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="1"
height="1"
id="mask210"><g
id="g212"><g
clip-path="url(#clipPath206)"
id="g214"><path
d="M 0,0 124,0 124,48 0,48 0,0 Z"
style="fill:#000000;fill-opacity:0.12000002;fill-rule:nonzero;stroke:none"
id="path216"
inkscape:connector-curvature="0" /></g></g></mask><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath222"><path
d="M 0,0 124,0 124,48 0,48 0,0 Z"
id="path224"
inkscape:connector-curvature="0" /></clipPath><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath226"><path
d="M 0,0 124,0 124,48 0,48 0,0 Z"
id="path228"
inkscape:connector-curvature="0" /></clipPath><pattern
patternTransform="matrix(1,0,0,-1,0,48)"
patternUnits="userSpaceOnUse"
x="0"
y="0"
width="124"
height="48"
id="pattern230"><g
id="g232" /><g
id="g234"><g
clip-path="url(#clipPath226)"
id="g236"><g
id="g238"><path
d="m 33.613,22.961 -3.988,-2.266 0.074,-0.074 3.914,2.223 c 0.559,0.316 0.836,0.734 0.836,1.156 -0.047,-0.379 -0.332,-0.75 -0.836,-1.039 z"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path240" /></g></g></g></pattern><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath254"><path
d="M 0,0 124,0 124,48 0,48 0,0 Z"
id="path256"
inkscape:connector-curvature="0" /></clipPath><mask
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="1"
height="1"
id="mask258"><g
id="g260"><g
clip-path="url(#clipPath254)"
id="g262"><path
d="M 0,0 124,0 124,48 0,48 0,0 Z"
style="fill:#000000;fill-opacity:0.25;fill-rule:nonzero;stroke:none"
id="path264"
inkscape:connector-curvature="0" /></g></g></mask><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath270"><path
d="M 0,0 124,0 124,48 0,48 0,0 Z"
id="path272"
inkscape:connector-curvature="0" /></clipPath><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath274"><path
d="M 0,0 124,0 124,48 0,48 0,0 Z"
id="path276"
inkscape:connector-curvature="0" /></clipPath><pattern
patternTransform="matrix(1,0,0,-1,0,48)"
patternUnits="userSpaceOnUse"
x="0"
y="0"
width="124"
height="48"
id="pattern278"><g
id="g280" /><g
id="g282"><g
clip-path="url(#clipPath274)"
id="g284"><g
id="g286"><path
d="m 18.012,33.902 15.601,-8.863 c 0.508,-0.289 0.789,-0.66 0.836,-1.039 0,0.418 -0.277,0.836 -0.836,1.156 L 18.012,34.02 c -1.117,0.632 -2.035,0.105 -2.035,-1.176 l 0,-0.114 c 0,1.278 0.918,1.805 2.035,1.172 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path288" /></g></g></g></pattern></defs><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1366"
inkscape:window-height="705"
id="namedview4"
showgrid="false"
inkscape:zoom="7.6276974"
inkscape:cx="93.965168"
inkscape:cy="29.61582"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="g10" /><g
id="g10"
inkscape:groupmode="layer"
inkscape:label="google-play-badge"
transform="matrix(1.25,0,0,-1.25,-9.4247625,49.85025)"><g
id="g12"
transform="matrix(1.0023923,0,0,0.99072975,-0.29664807,0)"><path
d="M 112,8 12,8 C 9.801,8 8,9.801 8,12 l 0,24 c 0,2.199 1.801,4 4,4 l 100,0 c 2.199,0 4,-1.801 4,-4 l 0,-24 c 0,-2.199 -1.801,-4 -4,-4 z"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path14"
inkscape:connector-curvature="0" /><path
d="m 112,39.359 c 1.852,0 3.359,-1.507 3.359,-3.359 l 0,-24 c 0,-1.852 -1.507,-3.359 -3.359,-3.359 l -100,0 c -1.852,0 -3.359,1.507 -3.359,3.359 l 0,24 c 0,1.852 1.507,3.359 3.359,3.359 l 100,0 M 112,40 12,40 C 9.801,40 8,38.199 8,36 L 8,12 C 8,9.801 9.801,8 12,8 l 100,0 c 2.199,0 4,1.801 4,4 l 0,24 c 0,2.199 -1.801,4 -4,4 z"
style="fill:#a6a6a6;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path16"
inkscape:connector-curvature="0" /><g
id="g18"
transform="matrix(1,0,0,-1,0,48)"><path
d="m 45.934,16.195 c 0,0.668 -0.2,1.203 -0.594,1.602 -0.453,0.473 -1.043,0.711 -1.766,0.711 -0.691,0 -1.281,-0.242 -1.765,-0.719 -0.485,-0.484 -0.727,-1.078 -0.727,-1.789 0,-0.711 0.242,-1.305 0.727,-1.785 0.484,-0.481 1.074,-0.723 1.765,-0.723 0.344,0 0.672,0.071 0.985,0.203 0.312,0.133 0.566,0.313 0.75,0.535 l -0.418,0.422 c -0.321,-0.379 -0.758,-0.566 -1.317,-0.566 -0.504,0 -0.941,0.176 -1.312,0.531 -0.367,0.356 -0.551,0.817 -0.551,1.383 0,0.566 0.184,1.031 0.551,1.387 0.371,0.351 0.808,0.531 1.312,0.531 0.535,0 0.985,-0.18 1.34,-0.535 0.234,-0.235 0.367,-0.559 0.402,-0.973 l -1.742,0 0,-0.578 2.324,0 c 0.028,0.125 0.036,0.246 0.036,0.363 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path20"
inkscape:connector-curvature="0" /></g><g
id="g22"
transform="matrix(1,0,0,-1,0,48)"><path
d="m 49.621,14.191 -2.183,0 0,1.52 1.968,0 0,0.578 -1.968,0 0,1.52 2.183,0 0,0.589 -2.801,0 0,-4.796 2.801,0 0,0.589 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path24"
inkscape:connector-curvature="0" /></g><g
id="g26"
transform="matrix(1,0,0,-1,0,48)"><path
d="m 52.223,18.398 -0.618,0 0,-4.207 -1.339,0 0,-0.589 3.297,0 0,0.589 -1.34,0 0,4.207 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path28"
inkscape:connector-curvature="0" /></g><g
id="g30"
transform="matrix(1,0,0,-1,0,48)"><path
d="m 55.949,18.398 0,-4.796 0.617,0 0,4.796 -0.617,0 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path32"
inkscape:connector-curvature="0" /></g><g
id="g34"
transform="matrix(1,0,0,-1,0,48)"><path
d="m 59.301,18.398 -0.613,0 0,-4.207 -1.344,0 0,-0.589 3.301,0 0,0.589 -1.344,0 0,4.207 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path36"
inkscape:connector-curvature="0" /></g><g
id="g38"
transform="matrix(1,0,0,-1,0,48)"><path
d="m 66.887,17.781 c -0.473,0.485 -1.059,0.727 -1.758,0.727 -0.703,0 -1.289,-0.242 -1.762,-0.727 C 62.895,17.297 62.66,16.703 62.66,16 c 0,-0.703 0.235,-1.297 0.707,-1.781 0.473,-0.485 1.059,-0.727 1.762,-0.727 0.695,0 1.281,0.242 1.754,0.731 0.476,0.488 0.711,1.078 0.711,1.777 0,0.703 -0.235,1.297 -0.707,1.781 z m -3.063,-0.402 c 0.356,0.359 0.789,0.539 1.305,0.539 0.512,0 0.949,-0.18 1.301,-0.539 0.355,-0.359 0.535,-0.82 0.535,-1.379 0,-0.559 -0.18,-1.02 -0.535,-1.379 -0.352,-0.359 -0.789,-0.539 -1.301,-0.539 -0.516,0 -0.949,0.18 -1.305,0.539 -0.355,0.359 -0.535,0.82 -0.535,1.379 0,0.559 0.18,1.02 0.535,1.379 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path40"
inkscape:connector-curvature="0" /></g><g
id="g42"
transform="matrix(1,0,0,-1,0,48)"><path
d="m 68.461,18.398 0,-4.796 0.75,0 2.332,3.73 0.027,0 -0.027,-0.922 0,-2.808 0.617,0 0,4.796 -0.644,0 -2.442,-3.914 -0.027,0 0.027,0.926 0,2.988 -0.613,0 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path44"
inkscape:connector-curvature="0" /></g><path
d="m 62.508,22.598 c -1.879,0 -3.414,-1.43 -3.414,-3.403 0,-1.957 1.535,-3.402 3.414,-3.402 1.883,0 3.418,1.445 3.418,3.402 0,1.973 -1.535,3.403 -3.418,3.403 z m 0,-5.465 c -1.031,0 -1.918,0.851 -1.918,2.062 0,1.227 0.887,2.063 1.918,2.063 1.031,0 1.922,-0.836 1.922,-2.063 0,-1.211 -0.891,-2.062 -1.922,-2.062 z m -7.449,5.465 c -1.883,0 -3.414,-1.43 -3.414,-3.403 0,-1.957 1.531,-3.402 3.414,-3.402 1.882,0 3.414,1.445 3.414,3.402 0,1.973 -1.532,3.403 -3.414,3.403 z m 0,-5.465 c -1.032,0 -1.922,0.851 -1.922,2.062 0,1.227 0.89,2.063 1.922,2.063 1.031,0 1.918,-0.836 1.918,-2.063 0,-1.211 -0.887,-2.062 -1.918,-2.062 z m -8.864,4.422 0,-1.446 3.453,0 c -0.101,-0.808 -0.371,-1.402 -0.785,-1.816 -0.504,-0.5 -1.289,-1.055 -2.668,-1.055 -2.125,0 -3.789,1.715 -3.789,3.84 0,2.125 1.664,3.84 3.789,3.84 1.149,0 1.985,-0.449 2.602,-1.031 l 1.019,1.019 c -0.863,0.824 -2.011,1.457 -3.621,1.457 -2.914,0 -5.363,-2.371 -5.363,-5.285 0,-2.914 2.449,-5.285 5.363,-5.285 1.575,0 2.758,0.516 3.688,1.484 0.953,0.953 1.25,2.293 1.25,3.375 0,0.336 -0.028,0.645 -0.078,0.903 l -4.86,0 z m 36.246,-1.121 c -0.281,0.761 -1.148,2.164 -2.914,2.164 -1.75,0 -3.207,-1.379 -3.207,-3.403 0,-1.906 1.442,-3.402 3.375,-3.402 1.563,0 2.465,0.953 2.836,1.508 l -1.16,0.773 c -0.387,-0.566 -0.914,-0.941 -1.676,-0.941 -0.757,0 -1.3,0.347 -1.648,1.031 l 4.551,1.883 -0.157,0.387 z m -4.64,-1.133 c -0.039,1.312 1.019,1.984 1.777,1.984 0.594,0 1.098,-0.297 1.266,-0.722 L 77.801,19.301 Z M 74.102,16 l 1.496,0 0,10 -1.496,0 0,-10 z m -2.45,5.84 -0.05,0 c -0.336,0.398 -0.977,0.758 -1.789,0.758 -1.704,0 -3.262,-1.496 -3.262,-3.414 0,-1.907 1.558,-3.391 3.262,-3.391 0.812,0 1.453,0.363 1.789,0.773 l 0.05,0 0,-0.488 c 0,-1.301 -0.695,-2 -1.816,-2 -0.914,0 -1.481,0.66 -1.715,1.215 L 66.82,14.75 c 0.375,-0.902 1.368,-2.012 3.016,-2.012 1.754,0 3.234,1.032 3.234,3.543 l 0,6.11 -1.418,0 0,-0.551 z m -1.711,-4.707 c -1.031,0 -1.894,0.863 -1.894,2.051 0,1.199 0.863,2.074 1.894,2.074 1.016,0 1.817,-0.875 1.817,-2.074 0,-1.188 -0.801,-2.051 -1.817,-2.051 z M 89.445,26 l -3.578,0 0,-10 1.492,0 0,3.789 2.086,0 c 1.657,0 3.282,1.199 3.282,3.106 0,1.906 -1.629,3.105 -3.282,3.105 z m 0.039,-4.82 -2.125,0 0,3.429 2.125,0 c 1.114,0 1.75,-0.925 1.75,-1.714 0,-0.774 -0.636,-1.715 -1.75,-1.715 z m 9.223,1.437 c -1.078,0 -2.199,-0.476 -2.66,-1.531 l 1.324,-0.555 c 0.285,0.555 0.809,0.735 1.363,0.735 0.774,0 1.559,-0.465 1.571,-1.286 l 0,-0.105 c -0.27,0.156 -0.848,0.387 -1.559,0.387 -1.426,0 -2.879,-0.785 -2.879,-2.25 0,-1.34 1.168,-2.203 2.481,-2.203 1.004,0 1.558,0.453 1.906,0.98 l 0.051,0 0,-0.773 1.441,0 0,3.836 c 0,1.773 -1.324,2.765 -3.039,2.765 z m -0.18,-5.48 c -0.488,0 -1.168,0.242 -1.168,0.847 0,0.774 0.848,1.071 1.582,1.071 0.657,0 0.965,-0.145 1.364,-0.336 -0.117,-0.926 -0.914,-1.582 -1.778,-1.582 z m 8.469,5.261 -1.715,-4.335 -0.051,0 -1.773,4.335 -1.609,0 2.664,-6.058 -1.52,-3.371 1.559,0 4.105,9.429 -1.66,0 z M 93.547,16 l 1.496,0 0,10 -1.496,0 0,-10 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path46"
inkscape:connector-curvature="0" /><g
id="g48"><path
d="M 16.348,33.969 C 16.113,33.723 15.977,33.34 15.977,32.844 l 0,-17.692 c 0,-0.496 0.136,-0.879 0.371,-1.125 l 0.058,-0.054 9.914,9.91 0,0.234 -9.914,9.91 -0.058,-0.058 z"
style="fill:url(#linearGradient50);fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path64"
inkscape:connector-curvature="0" /></g><g
id="g66"><path
d="m 29.621,20.578 -3.301,3.305 0,0.234 3.305,3.305 0.074,-0.043 3.914,-2.227 c 1.117,-0.632 1.117,-1.672 0,-2.308 l -3.914,-2.223 -0.078,-0.043 z"
style="fill:url(#linearGradient68);fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path78"
inkscape:connector-curvature="0" /></g><g
id="g80"><path
d="M 29.699,20.621 26.32,24 16.348,14.027 c 0.371,-0.39 0.976,-0.437 1.664,-0.047 l 11.687,6.641"
style="fill:url(#linearGradient82);fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path88"
inkscape:connector-curvature="0" /></g><g
id="g90"><path
d="M 29.699,27.379 18.012,34.02 c -0.688,0.386 -1.293,0.339 -1.664,-0.051 L 26.32,24 l 3.379,3.379 z"
style="fill:url(#linearGradient92);fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path104"
inkscape:connector-curvature="0" /></g><g
id="g106"><g
id="g108" /><g
id="g122"
mask="url(#mask114)"><g
id="g124" /><g
id="g146"><g
clip-path="url(#clipPath126)"
id="g148"><g
id="g150"><path
d="M 0,0 124,0 124,48 0,48 0,0 Z"
style="fill:url(#pattern134);fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path152"
inkscape:connector-curvature="0" /></g></g></g></g></g><g
id="g154"><g
id="g156" /><g
id="g170"
mask="url(#mask162)"><g
id="g172" /><g
id="g194"><g
clip-path="url(#clipPath174)"
id="g196"><g
id="g198"><path
d="M 0,0 124,0 124,48 0,48 0,0 Z"
style="fill:url(#pattern182);fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path200"
inkscape:connector-curvature="0" /></g></g></g></g></g><g
id="g202"><g
id="g204" /><g
id="g218"
mask="url(#mask210)"><g
id="g220" /><g
id="g242"><g
clip-path="url(#clipPath222)"
id="g244"><g
id="g246"><path
d="M 0,0 124,0 124,48 0,48 0,0 Z"
style="fill:url(#pattern230);fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path248"
inkscape:connector-curvature="0" /></g></g></g></g></g><g
id="g250"><g
id="g252" /><g
id="g266"
mask="url(#mask258)"><g
id="g268" /><g
id="g290"><g
clip-path="url(#clipPath270)"
id="g292"><g
id="g294"><path
d="M 0,0 124,0 124,48 0,48 0,0 Z"
style="fill:url(#pattern278);fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path296"
inkscape:connector-curvature="0" /></g></g></g></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -3,8 +3,6 @@ import { createStyles, Theme } from "@material-ui/core";
const styles = createStyles((theme: Theme) => ({
messageOuter: {
position: 'relative',
paddingTop: theme.spacing.unit * 2,
paddingBottom: theme.spacing.unit,
},
messageInner: {
width: '100%',

View File

@ -2,13 +2,13 @@ import { createStyles, Theme } from "@material-ui/core";
const styles = createStyles((theme: Theme) => ({
form: {
padding: '20px 0px',
paddingTop: theme.spacing.unit * 2,
},
field: {
width: '100%',
},
button: {
marginTop: '20px',
marginTop: theme.spacing.unit * 2,
width: '100%',
}
}));

View File

@ -1,14 +1,54 @@
import { createStyles, Theme } from "@material-ui/core";
const borderColor = '#e0e0e0';
const styles = createStyles((theme: Theme) => ({
secretContainer: {
width: '100%',
border: '1px solid ' + borderColor,
marginTop: theme.spacing.unit * 2,
marginBottom: theme.spacing.unit * 2,
},
qrcodeContainer: {
textAlign: 'center',
padding: theme.spacing.unit * 2,
},
textField: {
width: '100%',
base32Container: {
textAlign: 'center',
borderTop: '1px solid ' + borderColor,
padding: theme.spacing.unit,
wordWrap: 'break-word',
},
text: {
textAlign: 'center',
},
needGoogleAuthenticator: {
textAlign: 'center',
marginTop: theme.spacing.unit * 2,
},
needGoogleAuthenticatorText: {
fontSize: theme.typography.fontSize * 0.8,
},
store: {
width: '100px',
marginTop: theme.spacing.unit * 0.5,
marginLeft: theme.spacing.unit * 0.5,
marginRight: theme.spacing.unit * 0.5,
},
buttonContainer: {
textAlign: 'center',
paddingTop: theme.spacing.unit * 2,
},
progressContainer: {
textAlign: 'center',
paddingTop: theme.spacing.unit * 2,
},
button: {
marginLeft: theme.spacing.unit,
marginRight: theme.spacing.unit,
},
loginButtonContainer: {
textAlign: 'center',
},
}));

View File

@ -2,15 +2,13 @@ import { createStyles, Theme } from "@material-ui/core";
const styles = createStyles((theme: Theme) => ({
form: {
paddingTop: theme.spacing.unit * 2,
paddingBottom: theme.spacing.unit * 2,
marginTop: theme.spacing.unit * 2,
},
field: {
width: '100%',
marginBottom: theme.spacing.unit * 2,
},
button: {
marginTop: theme.spacing.unit * 2,
width: '100%',
}
}));

View File

@ -25,12 +25,25 @@ const styles = createStyles((theme: Theme) => ({
body: {
paddingTop: theme.spacing.unit * 2,
paddingBottom: theme.spacing.unit * 2,
paddingLeft: theme.spacing.unit,
paddingRight: theme.spacing.unit,
paddingLeft: theme.spacing.unit * 2,
paddingRight: theme.spacing.unit * 2,
border: '1px solid #e0e0e0',
borderRadius: '2px',
textAlign: 'justify',
},
methodName: {
fontSize: theme.typography.fontSize * 1.2,
fontWeight: 'bold',
marginBottom: theme.spacing.unit,
},
methodU2f: {
borderBottom: '1px solid #e0e0e0',
padding: theme.spacing.unit,
},
methodTotp: {
padding: theme.spacing.unit,
paddingTop: theme.spacing.unit * 2,
},
image: {
width: '120px',
},
@ -39,19 +52,17 @@ const styles = createStyles((theme: Theme) => ({
marginTop: theme.spacing.unit * 2,
marginBottom: theme.spacing.unit * 2,
},
footer: {
paddingTop: theme.spacing.unit,
fontSize: theme.typography.fontSize * 0.9,
},
registerDevice: {
float: 'right',
registerDeviceContainer: {
textAlign: 'right',
fontSize: theme.typography.fontSize * 0.8,
},
registerDevice: {},
totpField: {
marginTop: theme.spacing.unit * 2,
marginBottom: theme.spacing.unit * 2,
width: '100%',
},
totpButton: {
marginTop: theme.spacing.unit * 2,
width: '100%',
}
}));

View File

@ -10,6 +10,10 @@ const styles = createStyles((theme: Theme) => ({
width: '120px',
},
},
retryButtonContainer: {
textAlign: 'center',
paddingTop: theme.spacing.unit * 2,
},
}));
export default styles;

View File

@ -0,0 +1,148 @@
$brand-success: #5cb85c;
$brand-failure: #d44141;
$loader-size: 4em;
$check-height: $loader-size/2;
$check-width: $check-height/2;
$check-left: ($loader-size/6 + $loader-size/25);
$check-thickness: 3px;
$check-color: $brand-success;
$cross-height: $loader-size/2;
$cross-width: $check-height/10 - $check-height/12;
$cross-left: $loader-size/2;
$cross-top: $loader-size/4;
.circleLoader {
border: 1px solid rgba(0, 0, 0, 0.2);
border-left-color: $check-color;
animation: loader-spin 1.2s infinite linear;
position: relative;
display: inline-block;
vertical-align: top;
border-radius: 50%;
width: $loader-size;
height: $loader-size;
}
.loadComplete {
-webkit-animation: none;
animation: none;
transition: border 500ms ease-out;
&.success {
border-color: $brand-success;
}
&.failure {
border-color: $brand-failure;
}
}
.checkmark {
display: none;
&.show {
display: inline;
}
&.draw:after {
animation-duration: 800ms;
animation-timing-function: ease;
animation-name: checkmark;
transform: scaleX(-1) rotate(135deg);
}
&:after {
opacity: 1;
height: $check-height;
width: $check-width;
transform-origin: left top;
border-right: $check-thickness solid $check-color;
border-top: $check-thickness solid $check-color;
content: '';
left: $check-left;
top: $check-height;
position: absolute;
}
}
.cross {
display: none;
&.show {
display: inline;
}
&.draw:after, &.draw:before {
animation-duration: 300ms;
animation-timing-function: ease;
animation-name: cross;
}
&:before, &:after {
position: absolute;
left: $cross-left;
top: $cross-top;
content: '';
height: $cross-height;
width: $cross-width;
background-color: $brand-failure;
}
&:before {
transform: rotate(45deg);
}
&:after {
transform: rotate(-45deg);
}
}
@keyframes loader-spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes checkmark {
0% {
height: 0;
width: 0;
opacity: 1;
}
20% {
height: 0;
width: $check-width;
opacity: 1;
}
40% {
height: $check-height;
width: $check-width;
opacity: 1;
}
100% {
height: $check-height;
width: $check-width;
opacity: 1;
}
}
@keyframes cross {
0% {
width: 0;
height: 0;
}
20% {
height: 0;
width: $cross-width;
}
40% {
height: $cross-height;
width: $cross-width;
}
100% {
width: $cross-width;
height: $cross-height;
}
}

View File

@ -0,0 +1,44 @@
import React, { Component } from "react";
import classnames from 'classnames';
import styles from './CircleLoader.module.scss';
export enum Status {
LOADING,
SUCCESSFUL,
FAILURE,
}
export interface Props {
status: Status;
}
class CircleLoader extends Component<Props> {
render() {
const containerClasses = [styles.circleLoader];
const checkmarkClasses = [styles.checkmark, styles.draw];
const crossClasses = [styles.cross, styles.draw];
if (this.props.status === Status.SUCCESSFUL) {
containerClasses.push(styles.loadComplete);
containerClasses.push(styles.success);
checkmarkClasses.push(styles.show);
}
else if (this.props.status === Status.FAILURE) {
containerClasses.push(styles.loadComplete);
containerClasses.push(styles.failure);
crossClasses.push(styles.show);
}
const key = 'container-' + this.props.status;
return (
<div className={classnames(containerClasses)} key={key}>
{<div className={classnames(checkmarkClasses)} />}
{<div className={classnames(crossClasses)} />}
</div>
)
}
}
export default CircleLoader;

View File

@ -1,14 +1,14 @@
import { connect } from 'react-redux';
import StateSynchronizer, { OnLoaded, OnError } from '../../../components/StateSynchronizer/StateSynchronizer';
import { RootState } from '../../../reducers';
import { fetchStateSuccess, fetchState, fetchStateFailure } from '../../../reducers/Portal/actions';
import { fetchStateSuccess, fetchState, fetchStateFailure } from '../../../reducers/Portal/FirstFactor/actions';
import RemoteState from '../../../reducers/Portal/RemoteState';
import { Dispatch } from 'redux';
const mapStateToProps = (state: RootState) => ({
state: state.remoteState,
stateError: state.remoteStateError,
stateLoading: state.remoteStateLoading,
state: state.firstFactor.remoteState,
stateError: state.firstFactor.remoteStateError,
stateLoading: state.firstFactor.remoteStateLoading,
});
const mapDispatchToProps = (dispatch: Dispatch) => {

View File

@ -3,7 +3,7 @@ import PortalLayout from '../../../layouts/PortalLayout/PortalLayout';
import { RootState } from '../../../reducers';
const mapStateToProps = (state: RootState) => ({
authenticationLevel: (state.remoteState) ? state.remoteState.authentication_level : 0,
authenticationLevel: (state.firstFactor.remoteState) ? state.firstFactor.remoteState.authentication_level : 0,
});
export default connect(mapStateToProps)(PortalLayout);

View File

@ -1,12 +1,28 @@
import { connect } from 'react-redux';
import QueryString from 'query-string';
import FirstFactorView, { Props } from '../../../views/FirstFactorView/FirstFactorView';
import { Dispatch } from 'redux';
import { authenticateFailure, authenticateSuccess, authenticate } from '../../../reducers/Portal/actions';
import { authenticateFailure, authenticateSuccess, authenticate } from '../../../reducers/Portal/FirstFactor/actions';
import { RootState } from '../../../reducers';
const mapStateToProps = (state: RootState) => ({});
function redirect2FA(props: Props) {
if (!props.location) {
props.history.push('/2fa');
return;
}
const params = QueryString.parse(props.location.search);
if ('rd' in params) {
const rd = params['rd'] as string;
props.history.push(`/2fa?rd=${rd}`);
return;
}
props.history.push('/2fa');
}
function onAuthenticationRequested(dispatch: Dispatch, ownProps: Props) {
return async (username: string, password: string) => {
dispatch(authenticate());
@ -27,7 +43,7 @@ function onAuthenticationRequested(dispatch: Dispatch, ownProps: Props) {
return;
}
dispatch(authenticateSuccess());
ownProps.history.push('/2fa');
redirect2FA(ownProps);
});
}
}

View File

@ -1,10 +1,15 @@
import { connect } from 'react-redux';
import OneTimePasswordRegistrationView, { OnSuccess, OnFailure } from '../../../views/OneTimePasswordRegistrationView/OneTimePasswordRegistrationView';
import OneTimePasswordRegistrationView from '../../../views/OneTimePasswordRegistrationView/OneTimePasswordRegistrationView';
import { RootState } from '../../../reducers';
import { Dispatch } from 'redux';
import {to} from 'await-to-js';
import { generateTotpSecret, generateTotpSecretSuccess, generateTotpSecretFailure } from '../../../reducers/Portal/OneTimePasswordRegistration/actions';
import { Props } from '../../../views/OneTimePasswordRegistrationView/OneTimePasswordRegistrationView';
const mapStateToProps = (state: RootState) => ({});
const mapStateToProps = (state: RootState) => ({
error: state.oneTimePasswordRegistration.error,
secret: state.oneTimePasswordRegistration.secret,
});
async function checkIdentity(token: string) {
return fetch(`/api/secondfactor/totp/identity/finish?token=${token}`, {
@ -27,16 +32,35 @@ async function checkIdentity(token: string) {
});
}
const mapDispatchToProps = (dispatch: Dispatch) => {
async function tryGenerateTotpSecret(dispatch: Dispatch, token: string) {
let err, result;
dispatch(generateTotpSecret());
[err, result] = await to(checkIdentity(token));
if (err) {
const e = err;
setTimeout(() => {
dispatch(generateTotpSecretFailure(e.message));
}, 2000);
return;
}
dispatch(generateTotpSecretSuccess(result));
}
const mapDispatchToProps = (dispatch: Dispatch, ownProps: Props) => {
let internalToken: string;
return {
componentDidMount: async (token: string, onSuccess: OnSuccess, onFailure: OnFailure) => {
let err, result;
[err, result] = await to(checkIdentity(token));
if (err) {
onFailure(err);
return;
}
onSuccess(result.otpauth_url);
onInit: async (token: string) => {
internalToken = token;
await tryGenerateTotpSecret(dispatch, internalToken);
},
onRetryClicked: async () => {
await tryGenerateTotpSecret(dispatch, internalToken);
},
onCancelClicked: () => {
ownProps.history.push('/2fa');
},
onLoginClicked: () => {
ownProps.history.push('/2fa');
}
}
}

View File

@ -1,16 +1,20 @@
import { connect } from 'react-redux';
import QueryString from 'query-string';
import SecondFactorView, {Props} from '../../../views/SecondFactorView/SecondFactorView';
import { RootState } from '../../../reducers';
import { Dispatch } from 'redux';
import u2fApi, { SignResponse } from 'u2f-api';
import to from 'await-to-js';
import { logoutSuccess, logoutFailure, logout } from '../../../reducers/Portal/actions';
import { logoutSuccess, logoutFailure, logout, securityKeySignSuccess, securityKeySign, securityKeySignFailure, setSecurityKeySupported } from '../../../reducers/Portal/SecondFactor/actions';
import AuthenticationLevel from '../../../types/AuthenticationLevel';
import RemoteState from '../../../reducers/Portal/RemoteState';
const mapStateToProps = (state: RootState) => ({
state: state.remoteState,
stateError: state.remoteStateError,
state: state.firstFactor.remoteState,
stateError: state.firstFactor.remoteStateError,
securityKeySupported: state.secondFactor.securityKeySupported,
securityKeyVerified: state.secondFactor.securityKeySignSuccess || false,
securityKeyError: state.secondFactor.error,
});
async function requestSigning() {
@ -23,7 +27,7 @@ async function requestSigning() {
});
}
async function completeSigning(response: u2fApi.SignResponse) {
async function completeSecurityKeySigning(response: u2fApi.SignResponse) {
return fetch('/api/u2f/sign', {
method: 'POST',
headers: {
@ -39,25 +43,37 @@ async function completeSigning(response: u2fApi.SignResponse) {
});
}
async function triggerSecurityKeySigning() {
async function triggerSecurityKeySigning(dispatch: Dispatch, props: Props) {
let err, result;
dispatch(securityKeySign());
[err, result] = await to(requestSigning());
if (err) {
console.error(err);
dispatch(securityKeySignFailure(err.message));
return;
}
[err, result] = await to(u2fApi.sign(result, 60));
if (err) {
console.error(err);
dispatch(securityKeySignFailure(err.message));
return;
}
[err, result] = await to(completeSigning(result as SignResponse));
[err, result] = await to(completeSecurityKeySigning(result as SignResponse));
if (err) {
console.error(err);
dispatch(securityKeySignFailure(err.message));
return;
}
dispatch(securityKeySignSuccess());
await redirectUponAuthentication(props);
}
async function redirectUponAuthentication(props: Props) {
const params = QueryString.parse(props.history.location.search);
if ('rd' in params) {
setTimeout(() => {
window.location.replace(params['rd'] as string);
}, 1500);
}
}
const mapDispatchToProps = (dispatch: Dispatch, ownProps: Props) => {
@ -108,7 +124,11 @@ const mapDispatchToProps = (dispatch: Dispatch, ownProps: Props) => {
ownProps.history.push('/');
return;
}
await triggerSecurityKeySigning();
const isU2FSupported = await u2fApi.isSupported();
if (isU2FSupported) {
await dispatch(setSecurityKeySupported(true));
await triggerSecurityKeySigning(dispatch, ownProps);
}
}
}
}

View File

@ -4,8 +4,13 @@ import { RootState } from '../../../reducers';
import { Dispatch } from 'redux';
import {to} from 'await-to-js';
import * as U2fApi from "u2f-api";
import { Props } from '../../../views/SecurityKeyRegistrationView/SecurityKeyRegistrationView';
import { registerSecurityKey, registerSecurityKeyFailure, registerSecurityKeySuccess } from '../../../reducers/Portal/SecurityKeyRegistration/actions';
const mapStateToProps = (state: RootState) => ({});
const mapStateToProps = (state: RootState) => ({
deviceRegistered: state.securityKeyRegistration.success,
error: state.securityKeyRegistration.error,
});
async function checkIdentity(token: string) {
return fetch(`/api/secondfactor/u2f/identity/finish?token=${token}`, {
@ -39,32 +44,45 @@ async function completeRegistration(response: U2fApi.RegisterResponse) {
});
}
const mapDispatchToProps = (dispatch: Dispatch) => {
function fail(dispatch: Dispatch, err: Error) {
dispatch(registerSecurityKeyFailure(err.message));
}
const mapDispatchToProps = (dispatch: Dispatch, ownProps: Props) => {
return {
componentDidMount: async (token: string) => {
onInit: async (token: string) => {
let err, result;
dispatch(registerSecurityKey());
[err, result] = await to(checkIdentity(token));
if (err) {
console.error(err);
fail(dispatch, err);
return;
}
[err, result] = await to(requestRegistration());
if (err) {
console.error(err);
fail(dispatch, err);
return;
}
[err, result] = await to(U2fApi.register(result, [], 60));
if (err) {
console.error(err);
fail(dispatch, err);
return;
}
[err, result] = await to(completeRegistration(result as U2fApi.RegisterResponse));
if (err) {
console.error(err);
fail(dispatch, err);
return;
}
dispatch(registerSecurityKeySuccess());
setTimeout(() => {
ownProps.history.push('/2fa');
}, 2000);
},
onBackClicked: () => {
ownProps.history.push('/2fa');
}
}
}

View File

@ -6,11 +6,8 @@ import {
FETCH_STATE_REQUEST,
FETCH_STATE_SUCCESS,
FETCH_STATE_FAILURE,
LOGOUT_REQUEST,
LOGOUT_SUCCESS,
LOGOUT_FAILURE
} from "../constants";
import RemoteState from './RemoteState';
} from "../../constants";
import RemoteState from '../RemoteState';
/* FETCH_STATE */
export const fetchState = createAction(FETCH_STATE_REQUEST);
@ -31,11 +28,3 @@ export const authenticateSuccess = createAction(AUTHENTICATE_SUCCESS);
export const authenticateFailure = createAction(AUTHENTICATE_FAILURE, resolve => {
return (error: string) => resolve(error);
});
/* AUTHENTICATE_REQUEST */
export const logout = createAction(LOGOUT_REQUEST);
export const logoutSuccess = createAction(LOGOUT_SUCCESS);
export const logoutFailure = createAction(LOGOUT_FAILURE, resolve => {
return (error: string) => resolve(error);
});

View File

@ -1,7 +1,7 @@
import * as Actions from './actions';
import { ActionType, getType, StateType } from 'typesafe-actions';
import RemoteState from './RemoteState';
import RemoteState from '../RemoteState';
export type FirstFactorAction = ActionType<typeof Actions>;
@ -19,10 +19,6 @@ interface State {
remoteState: RemoteState | null;
remoteStateLoading: boolean;
remoteStateError: string | null;
logoutLoading: boolean;
logoutSuccess: boolean | null;
logoutError: string | null;
}
const initialState: State = {
@ -32,9 +28,6 @@ const initialState: State = {
remoteState: null,
remoteStateLoading: false,
remoteStateError: null,
logoutLoading: false,
logoutError: null,
logoutSuccess: null,
}
export type PortalState = StateType<State>;
@ -81,26 +74,6 @@ export default (state = initialState, action: FirstFactorAction) => {
remoteStateError: action.payload,
remoteStateLoading: false,
};
case getType(Actions.logout):
return {
...state,
logoutLoading: true,
logoutSuccess: null,
logoutError: null,
};
case getType(Actions.logoutSuccess):
return {
...state,
logoutLoading: false,
logoutSuccess: true,
};
case getType(Actions.logoutFailure):
return {
...state,
logoutLoading: false,
logoutError: action.payload,
}
}
return state;
}

View File

@ -0,0 +1,13 @@
import { createAction } from "typesafe-actions";
import { GENERATE_TOTP_SECRET_REQUEST, GENERATE_TOTP_SECRET_SUCCESS, GENERATE_TOTP_SECRET_FAILURE } from "../../constants";
import { Secret } from "../../../views/OneTimePasswordRegistrationView/Secret";
/* GENERATE_TOTP_SECRET_REQUEST */
export const generateTotpSecret = createAction(GENERATE_TOTP_SECRET_REQUEST);
export const generateTotpSecretSuccess = createAction(GENERATE_TOTP_SECRET_SUCCESS, resolve => {
return (secret: Secret) => resolve(secret);
});
export const generateTotpSecretFailure = createAction(GENERATE_TOTP_SECRET_FAILURE, resolve => {
return (error: string) => resolve(error);
});

View File

@ -0,0 +1,50 @@
import { ActionType, getType } from "typesafe-actions";
import * as Actions from './actions';
import { Secret } from "../../../views/OneTimePasswordRegistrationView/Secret";
type OneTimePasswordRegistrationAction = ActionType<typeof Actions>
export interface State {
loading: boolean;
error: string | null;
secret: Secret | null;
}
let initialState: State = {
loading: true,
error: null,
secret: null,
}
initialState = {
secret: {
base32_secret: 'PBSFWU2RM42HG3TNIRHUQMKSKVUW6NCNOBNFOLCFJZATS6CTI47A',
otpauth_url: 'PBSFWU2RM42HG3TNIRHUQMKSKVUW6NCNOBNFOLCFJZATS6CTI47A',
},
error: null,
loading: false,
}
export default (state = initialState, action: OneTimePasswordRegistrationAction) => {
switch(action.type) {
case getType(Actions.generateTotpSecret):
return {
loading: true,
error: null,
secret: null,
};
case getType(Actions.generateTotpSecretSuccess):
return {
...state,
loading: false,
secret: action.payload,
}
case getType(Actions.generateTotpSecretFailure):
return {
...state,
loading: false,
error: action.payload,
}
}
return state;
}

View File

@ -0,0 +1,26 @@
import { createAction } from "typesafe-actions";
import {
LOGOUT_REQUEST,
LOGOUT_SUCCESS,
LOGOUT_FAILURE,
SECURITY_KEY_SIGN,
SECURITY_KEY_SIGN_SUCCESS,
SECURITY_KEY_SIGN_FAILURE,
SET_SECURITY_KEY_SUPPORTED
} from "../../constants";
export const setSecurityKeySupported = createAction(SET_SECURITY_KEY_SUPPORTED, resolve => {
return (supported: boolean) => resolve(supported);
})
export const securityKeySign = createAction(SECURITY_KEY_SIGN);
export const securityKeySignSuccess = createAction(SECURITY_KEY_SIGN_SUCCESS);
export const securityKeySignFailure = createAction(SECURITY_KEY_SIGN_FAILURE, resolve => {
return (error: string) => resolve(error);
})
export const logout = createAction(LOGOUT_REQUEST);
export const logoutSuccess = createAction(LOGOUT_SUCCESS);
export const logoutFailure = createAction(LOGOUT_FAILURE, resolve => {
return (error: string) => resolve(error);
});

View File

@ -0,0 +1,75 @@
import * as Actions from './actions';
import { ActionType, getType, StateType } from 'typesafe-actions';
export type SecondFactorAction = ActionType<typeof Actions>;
interface State {
logoutLoading: boolean;
logoutSuccess: boolean | null;
error: string | null;
securityKeySupported: boolean;
securityKeySignLoading: boolean;
securityKeySignSuccess: boolean | null;
}
const initialState: State = {
logoutLoading: false,
logoutSuccess: null,
error: null,
securityKeySupported: false,
securityKeySignLoading: false,
securityKeySignSuccess: null,
}
export type PortalState = StateType<State>;
export default (state = initialState, action: SecondFactorAction): State => {
switch(action.type) {
case getType(Actions.logout):
return {
...state,
logoutLoading: true,
logoutSuccess: null,
error: null,
};
case getType(Actions.logoutSuccess):
return {
...state,
logoutLoading: false,
logoutSuccess: true,
};
case getType(Actions.logoutFailure):
return {
...state,
logoutLoading: false,
error: action.payload,
}
case getType(Actions.securityKeySign):
return {
...state,
securityKeySignLoading: true,
securityKeySignSuccess: false,
};
case getType(Actions.securityKeySignSuccess):
return {
...state,
securityKeySignLoading: false,
securityKeySignSuccess: true,
};
case getType(Actions.securityKeySignFailure):
return {
...state,
securityKeySignLoading: false,
securityKeySignSuccess: false,
};
case getType(Actions.setSecurityKeySupported):
return {
...state,
securityKeySupported: action.payload,
};
}
return state;
}

View File

@ -0,0 +1,8 @@
import { createAction } from "typesafe-actions";
import { REGISTER_SECURITY_KEY_REQUEST, REGISTER_SECURITY_KEY_SUCCESS, REGISTER_SECURITY_KEY_FAILURE } from "../../constants";
export const registerSecurityKey = createAction(REGISTER_SECURITY_KEY_REQUEST);
export const registerSecurityKeySuccess = createAction(REGISTER_SECURITY_KEY_SUCCESS);
export const registerSecurityKeyFailure = createAction(REGISTER_SECURITY_KEY_FAILURE, resolve => {
return (error: string) => resolve(error);
});

View File

@ -0,0 +1,36 @@
import { ActionType, getType } from "typesafe-actions";
import * as Actions from './actions';
type SecurityKeyRegistrationAction = ActionType<typeof Actions>
export interface State {
error: string | null;
success: boolean | null;
}
let initialState: State = {
error: null,
success: null,
}
export default (state = initialState, action: SecurityKeyRegistrationAction): State => {
switch(action.type) {
case getType(Actions.registerSecurityKey):
return {
success: null,
error: null,
};
case getType(Actions.registerSecurityKeySuccess):
return {
...state,
success: true,
};
case getType(Actions.registerSecurityKeyFailure):
return {
...state,
success: false,
error: action.payload,
};
}
return state;
}

View File

@ -0,0 +1,13 @@
import { combineReducers } from 'redux';
import FirstFactorReducer from './FirstFactor/reducer';
import SecondFactorReducer from './SecondFactor/reducer';
import OneTimePasswordRegistrationReducer from './OneTimePasswordRegistration/reducer';
import SecurityKeyRegistrationReducer from './SecurityKeyRegistration/reducer';
export default combineReducers({
firstFactor: FirstFactorReducer,
secondFactor: SecondFactorReducer,
oneTimePasswordRegistration: OneTimePasswordRegistrationReducer,
securityKeyRegistration: SecurityKeyRegistrationReducer,
});

View File

@ -7,6 +7,23 @@ export const AUTHENTICATE_REQUEST = '@portal/authenticate_request';
export const AUTHENTICATE_SUCCESS = '@portal/authenticate_success';
export const AUTHENTICATE_FAILURE = '@portal/authenticate_failure';
// SECOND FACTOR PAGE
export const SET_SECURITY_KEY_SUPPORTED = '@portal/second_factor/set_security_key_supported';
export const SECURITY_KEY_SIGN = '@portal/second_factor/security_key_sign';
export const SECURITY_KEY_SIGN_SUCCESS = '@portal/second_factor/security_key_sign_success';
export const SECURITY_KEY_SIGN_FAILURE = '@portal/second_factor/security_key_sign_failure';
export const LOGOUT_REQUEST = '@portal/logout_request';
export const LOGOUT_SUCCESS = '@portal/logout_success';
export const LOGOUT_FAILURE = '@portal/logout_failure';
// TOTP REGISTRATION
export const GENERATE_TOTP_SECRET_REQUEST = '@portal/generate_totp_secret_request';
export const GENERATE_TOTP_SECRET_SUCCESS = '@portal/generate_totp_secret_success';
export const GENERATE_TOTP_SECRET_FAILURE = '@portal/generate_totp_secret_failure';
// U2F REGISTRATION
export const REGISTER_SECURITY_KEY_REQUEST = '@portal/security_key_registration/register_request';
export const REGISTER_SECURITY_KEY_SUCCESS = '@portal/security_key_registration/register_success';
export const REGISTER_SECURITY_KEY_FAILURE = '@portal/security_key_registration/register_failed';

View File

@ -1,5 +1,6 @@
import PortalReducer, { PortalState } from './Portal/reducer';
import PortalReducer from './Portal';
import { StateType } from 'typesafe-actions';
export type RootState = PortalState;
export type RootState = StateType<typeof PortalReducer>;
export default PortalReducer;

View File

@ -7,7 +7,7 @@ import FormControlLabel from '@material-ui/core/FormControlLabel';
import Checkbox from '@material-ui/core/Checkbox';
import { Link } from "react-router-dom";
import { RouterProps } from "react-router";
import { RouterProps, RouteProps } from "react-router";
import { WithStyles, withStyles } from "@material-ui/core";
import firstFactorViewStyles from '../../assets/jss/views/FirstFactorView/FirstFactorView';
@ -18,7 +18,7 @@ import CheckBoxIcon from '@material-ui/icons/CheckBox';
import StateSynchronizer from "../../containers/components/StateSynchronizer/StateSynchronizer";
import RemoteState from "../../reducers/Portal/RemoteState";
export interface Props extends RouterProps, WithStyles {
export interface Props extends RouteProps, RouterProps, WithStyles {
onAuthenticationRequested(username: string, password: string): void;
}
@ -68,7 +68,7 @@ class FirstFactorView extends Component<Props, State> {
}
}
private renderWithState(state: RemoteState) {
private renderWithState() {
const { classes } = this.props;
return (
<div>
@ -137,7 +137,7 @@ class FirstFactorView extends Component<Props, State> {
<div>
<StateSynchronizer
onLoaded={(remoteState) => this.setState({remoteState})}/>
{this.state.remoteState ? this.renderWithState(this.state.remoteState) : null}
{this.state.remoteState ? this.renderWithState() : null}
</div>
)
}
@ -156,10 +156,6 @@ class FirstFactorView extends Component<Props, State> {
errorMessage: 'An error occured. Your username/password are probably wrong.'
});
}
onSuccess = () => {
this.props.history.push('/2fa');
}
}
export default withStyles(firstFactorViewStyles)(FirstFactorView);

View File

@ -20,7 +20,7 @@ class ForgotPasswordView extends Component<Props> {
label="E-mail">
</TextField>
<Button
onClick={() => this.props.history.push('/reset-password')}
onClick={() => this.props.history.push('/confirmation-sent')}
variant="contained"
color="primary"
className={classes.button}>

View File

@ -1,40 +1,38 @@
import React, { Component } from "react";
import { WithStyles, withStyles, TextField } from "@material-ui/core";
import { WithStyles, withStyles, CircularProgress, Button } from "@material-ui/core";
import styles from '../../assets/jss/views/OneTimePasswordRegistrationView/OneTimePasswordRegistrationView';
import { RouteProps } from "react-router";
import { RouteProps, RouterProps } from "react-router";
import QueryString from 'query-string';
import QRCode from 'qrcode.react';
import FormNotification from "../../components/FormNotification/FormNotification";
export type OnSuccess = (secret: Secret) => void;
export type OnFailure = (err: Error) => void;
import googleStoreImage from '../../assets/images/googleplay-badge.svg';
import appleStoreImage from '../../assets/images/applestore-badge.svg';
import { Secret } from "./Secret";
export interface Props extends WithStyles, RouteProps {
componentDidMount: (token: string, onSuccess: OnSuccess, onFailure: OnFailure) => void;
}
export interface Secret {
otp_url: string;
base32_secret: string;
}
interface State {
export interface Props extends WithStyles, RouteProps, RouterProps {
secret: Secret | null;
error: string | null;
onInit: (token: string) => void;
onRetryClicked: () => void;
onCancelClicked: () => void;
onLoginClicked: () => void;
}
class OneTimePasswordRegistrationView extends Component<Props, State> {
class OneTimePasswordRegistrationView extends Component<Props> {
private token: string | null;
constructor(props: Props) {
super(props);
this.state = {
secret: {
otp_url: 'https://coucou',
base32_secret: 'coucou',
},
}
this.token = null;
}
componentDidMount() {
componentWillMount() {
// If secret is already populated, we skip onInit (for testing purposes).
if (this.props.secret) return;
if (!this.props.location) {
console.error('There is no location to retrieve query params from...');
return;
@ -44,44 +42,87 @@ class OneTimePasswordRegistrationView extends Component<Props, State> {
console.error('Token parameter is expected and not provided');
return;
}
this.props.componentDidMount(
params['token'] as string,
this.onSuccess,
this.onFailure);
this.token = params['token'] as string;
this.props.onInit(this.token);
}
onSuccess = (secret: Secret) => {
this.setState({secret});
}
onFailure = (err: Error) => {}
private renderWithSecret(secret: Secret) {
const { classes } = this.props;
return (
<div>
<div className={classes.secretContainer}>
<TextField
id="totp-secret"
label="Key"
defaultValue={secret.base32_secret}
className={classes.textField}
margin="normal"
InputProps={{
readOnly: true,
}}
variant="outlined"
/>
<div className={classes.text}>
Register your device by scanning the barcode or adding the key.
</div>
<div className={classes.qrcodeContainer}>
{this.state.secret ? <QRCode value={this.state.secret.otp_url}></QRCode> : null}
<div className={classes.secretContainer}>
<div className={classes.qrcodeContainer}>
<QRCode value={secret.otpauth_url} size={180} level="Q"></QRCode>
</div>
<div className={classes.base32Container}>{secret.base32_secret}</div>
</div>
<div className={classes.loginButtonContainer}>
<Button
color="primary"
variant="contained"
onClick={this.props.onLoginClicked}>
Login
</Button>
</div>
<div className={classes.needGoogleAuthenticator}>
<div className={classes.needGoogleAuthenticatorText}>Need Google Authenticator?</div>
<img src={appleStoreImage} className={classes.store} alt='Google Authenticator on Apple Store'/>
<img src={googleStoreImage} className={classes.store} alt='Google Authenticator on Google Store'/>
</div>
</div>
)
}
private renderError() {
const {classes} = this.props;
return (
<div>
<FormNotification show={true}>
<div>{this.props.error}</div>
</FormNotification>
<div className={classes.buttonContainer}>
<Button
variant="contained"
color="primary"
className={classes.button}
onClick={this.props.onRetryClicked}>
Retry
</Button>
<Button
variant="contained"
color="primary"
className={classes.button}
onClick={this.props.onCancelClicked}>
Cancel
</Button>
</div>
</div>
);
}
private renderSecret() {
return this.props.secret
? this.renderWithSecret(this.props.secret)
: this.renderError();
}
private renderLoading() {
const { classes } = this.props;
return (
<div>
<div>One-Time password secret is being generated...</div>
<div className={classes.progressContainer}><CircularProgress /></div>
</div>
)
}
render() {
return this.state.secret ? this.renderWithSecret(this.state.secret) : null;
return !this.props.secret && !this.props.error
? this.renderLoading()
: this.renderSecret();
}
}

View File

@ -0,0 +1,5 @@
export interface Secret {
otpauth_url: string;
base32_secret: string;
}

View File

@ -16,12 +16,14 @@ class ResetPasswordView extends Component<Props> {
<TextField
className={classes.field}
variant="outlined"
type="password"
id="password1"
label="New password">
</TextField>
<TextField
className={classes.field}
variant="outlined"
type="password"
id="password2"
label="Confirm password">
</TextField>

View File

@ -3,16 +3,18 @@ import React, { Component } from 'react';
import { WithStyles, withStyles, Button, TextField } from '@material-ui/core';
import styles from '../../assets/jss/views/SecondFactorView/SecondFactorView';
import securityKeyImage from '../../assets/images/security-key-hand.png';
import StateSynchronizer from '../../containers/components/StateSynchronizer/StateSynchronizer';
import { RouterProps, Redirect } from 'react-router';
import RemoteState from '../../reducers/Portal/RemoteState';
import AuthenticationLevel from '../../types/AuthenticationLevel';
import { WithState } from '../../components/StateSynchronizer/WithState';
type Mode = 'u2f' | 'totp';
import CircleLoader, { Status } from '../../components/CircleLoader/CircleLoader';
export interface Props extends WithStyles, RouterProps, WithState {
securityKeySupported: boolean;
securityKeyVerified: boolean;
securityKeyError: string | null;
onLogoutClicked: () => void;
onRegisterSecurityKeyClicked: () => void;
onRegisterOneTimePasswordClicked: () => void;
@ -20,7 +22,6 @@ export interface Props extends WithStyles, RouterProps, WithState {
};
interface State {
mode: Mode;
remoteState: RemoteState | null;
}
@ -28,43 +29,53 @@ class SecondFactorView extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
mode: 'u2f',
remoteState: null,
}
}
private toggleMode = () => {
if (this.state.mode === 'u2f') {
this.setState({mode: 'totp'});
} else if (this.state.mode === 'totp') {
this.setState({mode: 'u2f'});
}
}
private renderU2f() {
private renderU2f(n: number) {
const { classes } = this.props;
let u2fStatus = Status.LOADING;
if (this.props.securityKeyVerified) {
u2fStatus = Status.SUCCESSFUL;
} else if (this.props.securityKeyError) {
u2fStatus = Status.FAILURE;
}
return (
<div>
<div className={classes.imageContainer}>
<img src={securityKeyImage} alt='security key' className={classes.image}/>
</div>
<div className={classes.methodU2f} key='u2f-method'>
<div className={classes.methodName}>Option {n} - Security Key</div>
<div>Insert your security key into a USB port and touch the gold disk.</div>
<div className={classes.imageContainer}>
<CircleLoader status={u2fStatus}></CircleLoader>
</div>
<div className={classes.registerDeviceContainer}>
<a className={classes.registerDevice} href="#"
onClick={this.props.onRegisterSecurityKeyClicked}>
Register device
</a>
</div>
</div>
)
}
private renderTotp() {
private renderTotp(n: number) {
const { classes } = this.props;
return (
<div>
<div>Provide a one-time password.</div>
<div className={classes.methodTotp} key='totp-method'>
<div className={classes.methodName}>Option {n} - One-Time Password</div>
<TextField
className={classes.totpField}
name="password"
id="password"
variant="outlined"
label="Password">
label="One-Time Password">
</TextField>
<div className={classes.registerDeviceContainer}>
<a className={classes.registerDevice} href="#"
onClick={this.props.onRegisterOneTimePasswordClicked}>
Register device
</a>
</div>
<Button
className={classes.totpButton}
variant="contained"
@ -76,20 +87,20 @@ class SecondFactorView extends Component<Props, State> {
}
private renderMode() {
if (this.state.mode === 'u2f') {
return this.renderU2f();
} else if (this.state.mode === 'totp') {
return this.renderTotp();
const { classes } = this.props;
const methods = [];
let n = 1;
if (this.props.securityKeySupported) {
methods.push(this.renderU2f(n));
n++;
}
}
methods.push(this.renderTotp(n));
private onRegisterClicked = () => {
const mode = this.state.mode;
if (mode === 'u2f') {
this.props.onRegisterSecurityKeyClicked();
} else {
this.props.onRegisterOneTimePasswordClicked();
}
return (
<div className={classes.methodsContainer}>
{methods}
</div>
);
}
private renderWithState(state: RemoteState) {
@ -109,17 +120,6 @@ class SecondFactorView extends Component<Props, State> {
<div className={classes.body}>
{this.renderMode()}
</div>
<div className={classes.footer}>
<a
className={classes.otherMethod}
href="#"
onClick={this.toggleMode}>
{this.state.mode === 'u2f' ? 'Use one-time password' : 'Use security key'}
</a>
<a className={classes.registerDevice} href="#" onClick={this.onRegisterClicked}>
Register device
</a>
</div>
</div>
)
}

View File

@ -1,18 +1,24 @@
import React, { Component } from "react";
import securityKeyImage from '../../assets/images/security-key-hand.png';
import { WithStyles, withStyles } from "@material-ui/core";
import { WithStyles, withStyles, Button } from "@material-ui/core";
import styles from '../../assets/jss/views/SecurityKeyRegistrationView/SecurityKeyRegistrationView';
import { RouteProps } from "react-router";
import { RouteProps, RouterProps } from "react-router";
import QueryString from 'query-string';
import FormNotification from "../../components/FormNotification/FormNotification";
import CircleLoader, { Status } from "../../components/CircleLoader/CircleLoader";
interface Props extends WithStyles, RouteProps {
componentDidMount: (token: string) => void;
export interface Props extends WithStyles, RouteProps, RouterProps {
deviceRegistered: boolean | null;
error: string | null;
onInit: (token: string) => void;
onBackClicked: () => void;
}
class SecurityKeyRegistrationView extends Component<Props> {
componentDidMount() {
if (this.props.deviceRegistered) return;
if (!this.props.location) {
console.error('There is no location to retrieve query params from...');
return;
@ -22,20 +28,53 @@ class SecurityKeyRegistrationView extends Component<Props> {
console.error('Token parameter is expected and not provided');
return;
}
this.props.componentDidMount(params['token'] as string);
this.props.onInit(params['token'] as string);
}
render() {
const {classes} = this.props;
private renderError() {
const { classes } = this.props;
return (
<div>
<div className={classes.infoContainer}>Press the gold disk to register your security key</div>
<div className={classes.imageContainer}>
<img src={securityKeyImage} alt="security key" />
<FormNotification show={true}>
{this.props.error}
</FormNotification>
<div className={classes.retryButtonContainer}>
<Button
variant="contained"
color="primary"
onClick={this.props.onBackClicked}>
Back
</Button>
</div>
</div>
)
}
private renderRegistering() {
const { classes } = this.props;
let status = Status.LOADING;
if (this.props.deviceRegistered === true) {
status = Status.SUCCESSFUL;
} else if (this.props.error) {
status = Status.FAILURE;
}
return (
<div>
<div className={classes.infoContainer}>Press the gold disk to register your security key</div>
<div className={classes.imageContainer}>
<CircleLoader status={status}></CircleLoader>
</div>
</div>
)
}
render() {
if (this.props.error) {
return this.renderError();
}
return this.renderRegistering();
}
}
export default withStyles(styles)(SecurityKeyRegistrationView);