From 6b133227d703dc59fe85e594d072d0ca6fff5b9a Mon Sep 17 00:00:00 2001 From: Mrugesh Mohapatra Date: Thu, 17 May 2018 03:36:11 +0530 Subject: [PATCH 01/12] feat(auth0): Add packages, basic setup, etc --- package-lock.json | 147 ++++++++++++++++++++++++++++++++++- package.json | 2 + server/passport-providers.js | 16 +++- 3 files changed, 162 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4710aa2848..a5233aa023 100644 --- a/package-lock.json +++ b/package-lock.json @@ -721,6 +721,32 @@ "integrity": "sha1-lfE2KbEsOlGl0hWr3OKqnzL4B3M=", "dev": true }, + "auth0-js": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/auth0-js/-/auth0-js-9.5.1.tgz", + "integrity": "sha1-NN6msPEbXl7hOWBWEfSbHA8V27E=", + "requires": { + "base64-js": "1.3.0", + "idtoken-verifier": "1.2.0", + "js-cookie": "2.2.0", + "qs": "6.5.1", + "superagent": "3.8.3", + "url-join": "1.1.0", + "winchan": "0.2.0" + }, + "dependencies": { + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" + }, + "url-join": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-1.1.0.tgz", + "integrity": "sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg=" + } + } + }, "aws-sdk": { "version": "2.141.0", "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.141.0.tgz", @@ -3889,6 +3915,11 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "cookiejar": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.1.tgz", + "integrity": "sha1-Qa1XsbVVlR7BcUEqgZQrHoIA00o=" + }, "copy-concurrently": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", @@ -4225,6 +4256,11 @@ "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-1.0.9.tgz", "integrity": "sha1-zFRJaF37hesRyYKKzHy4erW7/MA=" }, + "crypto-js": { + "version": "3.1.9-1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.9-1.tgz", + "integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg=" + }, "crypto-random-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", @@ -8806,6 +8842,30 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" }, + "idtoken-verifier": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/idtoken-verifier/-/idtoken-verifier-1.2.0.tgz", + "integrity": "sha512-8jmmFHwdPz8L73zGNAXHHOV9yXNC+Z0TUBN5rafpoaFaLFltlIFr1JkQa3FYAETP23eSsulVw0sBiwrE8jqbUg==", + "requires": { + "base64-js": "1.3.0", + "crypto-js": "3.1.9-1", + "jsbn": "0.1.1", + "superagent": "3.8.3", + "url-join": "1.1.0" + }, + "dependencies": { + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" + }, + "url-join": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-1.1.0.tgz", + "integrity": "sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg=" + } + } + }, "ieee754": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", @@ -9628,6 +9688,11 @@ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.1.1.tgz", "integrity": "sha1-NHwcIcfgBBFeCk2jLOzgQfrTyKM=" }, + "js-cookie": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.0.tgz", + "integrity": "sha1-Gywnmm7s44ChIWi5JIUmWzWx7/s=" + }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", @@ -9656,8 +9721,7 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "jschardet": { "version": "1.6.0", @@ -13935,6 +13999,16 @@ "pause": "0.0.1" } }, + "passport-auth0": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/passport-auth0/-/passport-auth0-0.6.1.tgz", + "integrity": "sha512-JJzb7BhR7eH19h9awD1+rZQbo0FCPMH1Wf6cGcSnw+boGYaIWc+A3ID0KDd6A8eFwqDEZgy+NEuYhIGOpngFuA==", + "requires": { + "passport-oauth": "1.0.0", + "request": "2.83.0", + "xtend": "4.0.1" + } + }, "passport-facebook": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/passport-facebook/-/passport-facebook-2.1.1.tgz", @@ -18143,6 +18217,70 @@ "chalk": "1.1.3" } }, + "superagent": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "requires": { + "component-emitter": "1.2.1", + "cookiejar": "2.1.1", + "debug": "3.1.0", + "extend": "3.0.1", + "form-data": "2.3.1", + "formidable": "1.2.1", + "methods": "1.1.2", + "mime": "1.4.1", + "qs": "6.5.1", + "readable-stream": "2.3.6" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "formidable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", + "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "5.1.1" + } + } + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -20339,6 +20477,11 @@ "semver": "5.4.1" } }, + "winchan": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/winchan/-/winchan-0.2.0.tgz", + "integrity": "sha1-OGMCjn+XSw2hQS8oQXukJJcqvZQ=" + }, "window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", diff --git a/package.json b/package.json index e3099a89b8..f9cfb30183 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "dependencies": { "@freecodecamp/loopback-component-passport": "^1.0.0", "accepts": "^1.3.0", + "auth0-js": "^9.5.1", "babel-core": "^6.18.1", "babel-preset-es2015": "^6.3.13", "babel-preset-react": "^6.3.13", @@ -101,6 +102,7 @@ "normalizr": "2.2.1", "opbeat": "^4.14.0", "passport": "^0.4.0", + "passport-auth0": "^0.6.1", "passport-facebook": "^2.0.0", "passport-github": "^1.0.0", "passport-google-oauth2": "~0.1.6", diff --git a/server/passport-providers.js b/server/passport-providers.js index 733d1b845c..59816d6c60 100644 --- a/server/passport-providers.js +++ b/server/passport-providers.js @@ -1,4 +1,4 @@ -const successRedirect = '/'; +const successRedirect = '/settings'; const failureRedirect = '/signin'; const linkSuccessRedirect = '/settings'; const linkFailureRedirect = '/settings'; @@ -165,5 +165,19 @@ export default { 'We\'ve updated your profile based ', 'on your your GitHub account.' ].join('') + }, + 'auth0-login': { + provider: 'auth0', + module: 'passport-auth0', + clientID: process.env.AUTH0_CLIENT_ID, + clientSecret: process.env.AUTH0_CLIENT_SECRET, + domain: process.env.AUTH0_DOMAIN, + callbackURL: '/auth/auth0/callback', + authPath: '/auth/auth0', + callbackPath: '/auth/auth0/callback', + successRedirect: successRedirect, + failureRedirect: failureRedirect, + scope: ['email'], + failureFlash: true } }; From ebc158622250e002a1b33d6d556bc8a63d5a7d9b Mon Sep 17 00:00:00 2001 From: Mrugesh Mohapatra Date: Fri, 18 May 2018 10:45:53 +0530 Subject: [PATCH 02/12] fix(sign-up): Add check boxes for confirm --- client/less/main.less | 48 ++++++++++++++++++++----- server/boot/authentication.js | 12 +++---- server/views/account/email-signin.jade | 50 +++++++++++++++++++++++--- 3 files changed, 91 insertions(+), 19 deletions(-) diff --git a/client/less/main.less b/client/less/main.less index db9a804e05..bbf9249e6b 100644 --- a/client/less/main.less +++ b/client/less/main.less @@ -35,14 +35,46 @@ pre.wrappable { word-wrap: break-word; /* IE 5+ */ } -//input[type=checkbox] { -// /* Double-sized Checkboxes */ -// -ms-transform: scale(2); /* IE */ -// -moz-transform: scale(2); /* FF */ -// -webkit-transform: scale(2); /* Safari and Chrome */ -// -o-transform: scale(2); /* Opera */ -// padding: 10px; -//} +.checkbox label:after { + content: ''; + display: table; + clear: both; +} + +.checkbox .cr { + position: relative; + display: inline-block; + border: 1px solid #a9a9a9; + border-radius: .25em; + width: 1.3em; + height: 1.3em; + float: left; + margin-right: .5em; +} + +.checkbox .cr .cr-icon { + position: absolute; + font-size: .8em; + line-height: 0; + top: 50%; + left: 15%; +} + +.checkbox label input[type="checkbox"] { + display: none; +} + +.checkbox label input[type="checkbox"]+.cr>.cr-icon { + opacity: 0; +} + +.checkbox label input[type="checkbox"]:checked+.cr>.cr-icon { + opacity: 1; +} + +.checkbox label input[type="checkbox"]:disabled+.cr { + opacity: .5; +} .btn-group { border-color: @brand-primary; diff --git a/server/boot/authentication.js b/server/boot/authentication.js index 100880e0a4..38ceaa63de 100644 --- a/server/boot/authentication.js +++ b/server/boot/authentication.js @@ -29,10 +29,10 @@ module.exports = function enableAuthentication(app) { const api = app.loopback.Router(); const { AuthToken, User } = app.models; - router.get('/login', (req, res) => res.redirect(301, '/signin')); - router.get('/logout', (req, res) => res.redirect(301, '/signout')); - router.get('/signup', (req, res) => res.redirect(301, '/signin')); - router.get('/email-signin', (req, res) => res.redirect(301, '/signin')); + router.get('/signup', (req, res) => res.redirect(301, '/login')); + router.get('/email-signin', (req, res) => res.redirect(301, '/login')); + router.get('/signin', (req, res) => res.redirect(301, '/login')); + router.get('/signout', (req, res) => res.redirect(301, '/logout')); function getEmailSignin(req, res) { if (isSignUpDisabled) { @@ -45,9 +45,9 @@ module.exports = function enableAuthentication(app) { }); } - router.get('/signin', ifUserRedirect, getEmailSignin); + router.get('/login', ifUserRedirect, getEmailSignin); - router.get('/signout', (req, res) => { + router.get('/logout', (req, res) => { req.logout(); res.redirect('/'); }); diff --git a/server/views/account/email-signin.jade b/server/views/account/email-signin.jade index cf1fb37390..fee55113fd 100644 --- a/server/views/account/email-signin.jade +++ b/server/views/account/email-signin.jade @@ -17,9 +17,26 @@ block content input(type='hidden', name='_csrf', value=_csrf) .form-group input.input-lg.form-control(type='email', name='email', id='email', placeholder='Enter your email address', autofocus=true, required, oninvalid="this.setCustomValidity('Enter your email address')", oninput="setCustomValidity('')") - .button-spacer - button#magic-btn.btn.btn-primary.btn-lg.btn-block(type='submit') - | Get a sign in link + .button-spacer + div.checkbox + label + input#terms-privacy(type='checkbox', name='terms-privacy', checked=false) + span.cr + i.cr-icon.fa.fa-check + | I accept the + a(href="/terms" target="_blank") Terms of Service + | and + a(href="/privacy" target="_blank") Privacy Policy + | (required) + div.checkbox + label + input(type='checkbox', name='quincy-emails', checked=false) + span.cr + i.cr-icon.fa.fa-check + | I want weekly emails from Quincy (freeCodeCamp.org's founder) + .button-spacer + button#magic-btn.btn.btn-primary.btn-lg.btn-block(type='submit') + | Get a sign in link .row .col-sm-6.col-sm-offset-3 br @@ -30,7 +47,7 @@ block content script. $(document).ready(function() { - function disableMagicButton (isDisabled) { + function disableMagicButton (isDisabled, isLaunched) { if (isDisabled) { $('#magic-btn') .prop('disabled', true) @@ -41,7 +58,30 @@ block content .html('If you didn\'t get the email, check your spam folder, or reload the page to try again.'); } } - + + function disableMagicButtonForAgreement (isLaunched) { + if(isLaunched) { + $('#magic-btn') + .prop('disabled', true) + .html('Get a sign in link'); + return; + } else { + $('#magic-btn') + .prop('disabled', false) + .html('Get a sign in link'); + } + } + + disableMagicButtonForAgreement(true); + + $('#terms-privacy').click(function(){ + if(this.checked) { + disableMagicButtonForAgreement(false); + } else { + disableMagicButtonForAgreement(true); + } + }); + $('form').submit(function(event){ event.preventDefault(); $('#flash-board').hide(); From ed8e8091b28edf5f1f5e4d7cf8eae9e480e3d41a Mon Sep 17 00:00:00 2001 From: Mrugesh Mohapatra Date: Fri, 18 May 2018 19:01:02 +0530 Subject: [PATCH 03/12] chore: cleanup old public files and add logo --- public/css/Vimeo.css | 97 --- public/css/themes/flatly.less | 1167 --------------------------- public/css/themes/modern.less | 151 ---- public/favicon.ico | Bin 7406 -> 1150 bytes public/favicons.html | 17 - public/images/freeCodeCamp-puck.svg | 37 + public/images/freecodecamp_logo.svg | 199 ----- 7 files changed, 37 insertions(+), 1631 deletions(-) delete mode 100644 public/css/Vimeo.css delete mode 100644 public/css/themes/flatly.less delete mode 100644 public/css/themes/modern.less delete mode 100644 public/favicons.html create mode 100644 public/images/freeCodeCamp-puck.svg delete mode 100644 public/images/freecodecamp_logo.svg diff --git a/public/css/Vimeo.css b/public/css/Vimeo.css deleted file mode 100644 index 1bdb4b99bd..0000000000 --- a/public/css/Vimeo.css +++ /dev/null @@ -1,97 +0,0 @@ -.vimeo-image, -.vimeo-image:after, -.vimeo-embed { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; -} -.vimeo { - position: relative; - padding-bottom: 56.25%; - background: #e2e2e2; -} -.vimeo iframe { - border: 0; -} -.vimeo-image { - background-position: center center; - background-size: 100% auto; -} -.vimeo-image:after { - z-index: 1; - display: block; - content: ''; - background: rgba(0,0,0,0.3); -} -.vimeo-play-button, -.vimeo-loading { - top: 50%; - left: 50%; - transform: translateX(-50%) translateY(-50%); -} -.vimeo-play-button { - z-index: 2; - position: absolute; - padding: 0; - width: 70px; - border: 0; - background: none; -} -.vimeo-play-button:focus { - outline: none; -} -.vimeo-play-button svg { - fill: #fff; - -webkit-filter: drop-shadow(0 1px 1px rgba(0,0,0,0.8)); - filter: drop-shadow(0 1px 1px rgba(0,0,0,0.8)); -} -.vimeo-loading { - z-index: 4; - position: absolute; - width: 32px; - height: 32px; -} -.vimeo-loading svg { - fill: #000; - transform-origin: 50% 50%; - -webkit-animation: spinner 0.8s infinite linear; - animation: spinner 0.8s infinite linear; -} -.vimeo-embed iframe { - width: 100%; - height: 100%; -} -@-moz-keyframes spinner { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} -@-webkit-keyframes spinner { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} -@-o-keyframes spinner { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} -@keyframes spinner { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/public/css/themes/flatly.less b/public/css/themes/flatly.less deleted file mode 100644 index 41eb6e716c..0000000000 --- a/public/css/themes/flatly.less +++ /dev/null @@ -1,1167 +0,0 @@ -// Flatly 3.3.0 -// Variables -// -------------------------------------------------- - -//== Colors -// -//## Gray and brand colors for use across Bootstrap. - -@gray-base: #000; -@gray-darker: lighten(@gray-base, 13.5%); // #222 -@gray-dark: #7b8a8b; // #333 -@gray: #95a5a6; // #555 -@gray-light: #b4bcc2; // #999 -@gray-lighter: #ecf0f1; // #eee - -@brand-primary: #2C3E50; -@brand-success: #18BC9C; -@brand-info: #3498DB; -@brand-warning: #F39C12; -@brand-danger: #E74C3C; - - -//== Scaffolding -// -//## Settings for some of the most global styles. - -//** Background color for ``. -@body-bg: #fff; -//** Global text color on ``. -@text-color: @brand-primary; - -//** Global textual link color. -@link-color: @brand-success; -//** Link hover color set via `darken()` function. -@link-hover-color: @link-color; -//** Link hover decoration. -@link-hover-decoration: underline; - - -//== Typography -// -//## Font, line-height, and color for body text, headings, and more. - -@font-family-sans-serif: "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif; -@font-family-serif: Georgia, "Times New Roman", Times, serif; -//** Default monospace fonts for ``, ``, and `
`.
-@font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace;
-@font-family-base:        @font-family-sans-serif;
-
-@font-size-base:          15px;
-@font-size-large:         ceil((@font-size-base * 1.25)); // ~18px
-@font-size-small:         ceil((@font-size-base * 0.85)); // ~12px
-
-@font-size-h1:            floor((@font-size-base * 2.6)); // ~36px
-@font-size-h2:            floor((@font-size-base * 2.15)); // ~30px
-@font-size-h3:            ceil((@font-size-base * 1.7)); // ~24px
-@font-size-h4:            ceil((@font-size-base * 1.25)); // ~18px
-@font-size-h5:            @font-size-base;
-@font-size-h6:            ceil((@font-size-base * 0.85)); // ~12px
-
-//** Unit-less `line-height` for use in components like buttons.
-@line-height-base:        1.428571429; // 20/14
-//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
-@line-height-computed:    floor((@font-size-base * @line-height-base)); // ~20px
-
-//** By default, this inherits from the ``.
-@headings-font-family:    @font-family-base;
-@headings-font-weight:    400;
-@headings-line-height:    1.1;
-@headings-color:          inherit;
-
-
-//== Iconography
-//
-//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via npm.
-
-//** Load fonts from this directory.
-@icon-font-path:          "../fonts/";
-//** File name for all font files.
-@icon-font-name:          "glyphicons-halflings-regular";
-//** Element ID within SVG icon file.
-@icon-font-svg-id:        "glyphicons_halflingsregular";
-
-
-//== Components
-//
-//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
-
-@padding-base-vertical:     10px;
-@padding-base-horizontal:   15px;
-
-@padding-large-vertical:    18px;
-@padding-large-horizontal:  27px;
-
-@padding-small-vertical:    6px;
-@padding-small-horizontal:  9px;
-
-@padding-xs-vertical:       1px;
-@padding-xs-horizontal:     5px;
-
-@line-height-large:         1.33;
-@line-height-small:         1.5;
-
-@border-radius-base:        4px;
-@border-radius-large:       6px;
-@border-radius-small:       3px;
-
-//** Global color for active items (e.g., navs or dropdowns).
-@component-active-color:    #fff;
-//** Global background color for active items (e.g., navs or dropdowns).
-@component-active-bg:       @brand-primary;
-
-//** Width of the `border` for generating carets that indicator dropdowns.
-@caret-width-base:          4px;
-//** Carets increase slightly in size for larger components.
-@caret-width-large:         5px;
-
-
-//== Tables
-//
-//## Customizes the `.table` component with basic values, each used across all table variations.
-
-//** Padding for ``s and ``s.
-@table-cell-padding:            8px;
-//** Padding for cells in `.table-condensed`.
-@table-condensed-cell-padding:  5px;
-
-//** Default background color used for all tables.
-@table-bg:                      transparent;
-//** Background color used for `.table-striped`.
-@table-bg-accent:               #f9f9f9;
-//** Background color used for `.table-hover`.
-@table-bg-hover:                @gray-lighter;
-@table-bg-active:               @table-bg-hover;
-
-//** Border color for table and cell borders.
-@table-border-color:            @gray-lighter;
-
-
-//== Buttons
-//
-//## For each of Bootstrap's buttons, define text, background and border color.
-
-@btn-font-weight:                normal;
-
-@btn-default-color:              #fff;
-@btn-default-bg:                 @gray;
-@btn-default-border:             @btn-default-bg;
-
-@btn-primary-color:              @btn-default-color;
-@btn-primary-bg:                 @brand-primary;
-@btn-primary-border:             @btn-primary-bg;
-
-@btn-success-color:              @btn-default-color;
-@btn-success-bg:                 @brand-success;
-@btn-success-border:             @btn-success-bg;
-
-@btn-info-color:                 @btn-default-color;
-@btn-info-bg:                    @brand-info;
-@btn-info-border:                @btn-info-bg;
-
-@btn-warning-color:              @btn-default-color;
-@btn-warning-bg:                 @brand-warning;
-@btn-warning-border:             @btn-warning-bg;
-
-@btn-danger-color:               @btn-default-color;
-@btn-danger-bg:                  @brand-danger;
-@btn-danger-border:              @btn-danger-bg;
-
-@btn-link-disabled-color:        @gray-light;
-
-
-//== Forms
-//
-//##
-
-//** `` background color
-@input-bg:                       #fff;
-//** `` background color
-@input-bg-disabled:              @gray-lighter;
-
-//** Text color for ``s
-@input-color:                    @text-color;
-//** `` border color
-@input-border:                   #dce4ec;
-
-// TODO: Rename `@input-border-radius` to `@input-border-radius-base` in v4
-//** Default `.form-control` border radius
-@input-border-radius:            @border-radius-base;
-//** Large `.form-control` border radius
-@input-border-radius-large:      @border-radius-large;
-//** Small `.form-control` border radius
-@input-border-radius-small:      @border-radius-small;
-
-//** Border color for inputs on focus
-@input-border-focus:             @brand-primary;
-
-//** Placeholder text color
-@input-color-placeholder:        #acb6c0;
-
-//** Default `.form-control` height
-@input-height-base:              (@line-height-computed + (@padding-base-vertical * 2) + 2);
-//** Large `.form-control` height
-@input-height-large:             (ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2);
-//** Small `.form-control` height
-@input-height-small:             (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2);
-
-@legend-color:                   @text-color;
-@legend-border-color:            transparent;
-
-//** Background color for textual input addons
-@input-group-addon-bg:           @gray-lighter;
-//** Border color for textual input addons
-@input-group-addon-border-color: @input-border;
-
-//** Disabled cursor for form controls and buttons.
-@cursor-disabled:                not-allowed;
-
-
-//== Dropdowns
-//
-//## Dropdown menu container and contents.
-
-//** Background for the dropdown menu.
-@dropdown-bg:                    #fff;
-//** Dropdown menu `border-color`.
-@dropdown-border:                rgba(0,0,0,.15);
-//** Dropdown menu `border-color` **for IE8**.
-@dropdown-fallback-border:       #ccc;
-//** Divider color for between dropdown items.
-@dropdown-divider-bg:            #e5e5e5;
-
-//** Dropdown link text color.
-@dropdown-link-color:            @gray-dark;
-//** Hover color for dropdown links.
-@dropdown-link-hover-color:      #fff;
-//** Hover background for dropdown links.
-@dropdown-link-hover-bg:         @dropdown-link-active-bg;
-
-//** Active dropdown menu item text color.
-@dropdown-link-active-color:     #fff;
-//** Active dropdown menu item background color.
-@dropdown-link-active-bg:        @component-active-bg;
-
-//** Disabled dropdown menu item background color.
-@dropdown-link-disabled-color:   @text-muted;
-
-//** Text color for headers within dropdown menus.
-@dropdown-header-color:          @text-muted;
-
-//** Deprecated `@dropdown-caret-color` as of v3.1.0
-@dropdown-caret-color:           #000;
-
-
-//-- Z-index master list
-//
-// Warning: Avoid customizing these values. They're used for a bird's eye view
-// of components dependent on the z-axis and are designed to all work together.
-//
-// Note: These variables are not generated into the Customizer.
-
-@zindex-navbar:            1000;
-@zindex-dropdown:          1000;
-@zindex-popover:           1060;
-@zindex-tooltip:           1070;
-@zindex-navbar-fixed:      1030;
-@zindex-modal:             1040;
-
-
-//== Media queries breakpoints
-//
-//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
-
-// Extra small screen / phone
-//** Deprecated `@screen-xs` as of v3.0.1
-@screen-xs:                  480px;
-//** Deprecated `@screen-xs-min` as of v3.2.0
-@screen-xs-min:              @screen-xs;
-//** Deprecated `@screen-phone` as of v3.0.1
-@screen-phone:               @screen-xs-min;
-
-// Small screen / tablet
-//** Deprecated `@screen-sm` as of v3.0.1
-@screen-sm:                  768px;
-@screen-sm-min:              @screen-sm;
-//** Deprecated `@screen-tablet` as of v3.0.1
-@screen-tablet:              @screen-sm-min;
-
-// Medium screen / desktop
-//** Deprecated `@screen-md` as of v3.0.1
-@screen-md:                  992px;
-@screen-md-min:              @screen-md;
-//** Deprecated `@screen-desktop` as of v3.0.1
-@screen-desktop:             @screen-md-min;
-
-// Large screen / wide desktop
-//** Deprecated `@screen-lg` as of v3.0.1
-@screen-lg:                  1200px;
-@screen-lg-min:              @screen-lg;
-//** Deprecated `@screen-lg-desktop` as of v3.0.1
-@screen-lg-desktop:          @screen-lg-min;
-
-// So media queries don't overlap when required, provide a maximum
-@screen-xs-max:              (@screen-sm-min - 1);
-@screen-sm-max:              (@screen-md-min - 1);
-@screen-md-max:              (@screen-lg-min - 1);
-
-
-//== Grid system
-//
-//## Define your custom responsive grid.
-
-//** Number of columns in the grid.
-@grid-columns:              12;
-//** Padding between columns. Gets divided in half for the left and right.
-@grid-gutter-width:         30px;
-// Navbar collapse
-//** Point at which the navbar becomes uncollapsed.
-@grid-float-breakpoint:     @screen-sm-min;
-//** Point at which the navbar begins collapsing.
-@grid-float-breakpoint-max: (@grid-float-breakpoint - 1);
-
-
-//== Container sizes
-//
-//## Define the maximum width of `.container` for different screen sizes.
-
-// Small screen / tablet
-@container-tablet:             (720px + @grid-gutter-width);
-//** For `@screen-sm-min` and up.
-@container-sm:                 @container-tablet;
-
-// Medium screen / desktop
-@container-desktop:            (940px + @grid-gutter-width);
-//** For `@screen-md-min` and up.
-@container-md:                 @container-desktop;
-
-// Large screen / wide desktop
-@container-large-desktop:      (1140px + @grid-gutter-width);
-//** For `@screen-lg-min` and up.
-@container-lg:                 @container-large-desktop;
-
-
-//== Navbar
-//
-//##
-
-// Basics of a navbar
-@navbar-height:                    60px;
-@navbar-margin-bottom:             @line-height-computed;
-@navbar-border-radius:             @border-radius-base;
-@navbar-padding-horizontal:        floor((@grid-gutter-width / 2));
-@navbar-padding-vertical:          ((@navbar-height - @line-height-computed) / 2);
-@navbar-collapse-max-height:       340px;
-
-@navbar-default-color:             #777;
-@navbar-default-bg:                @brand-primary;
-@navbar-default-border:            transparent;
-
-// Navbar links
-@navbar-default-link-color:                #fff;
-@navbar-default-link-hover-color:          @brand-success;
-@navbar-default-link-hover-bg:             transparent;
-@navbar-default-link-active-color:         #fff;
-@navbar-default-link-active-bg:            darken(@navbar-default-bg, 10%);
-@navbar-default-link-disabled-color:       #ccc;
-@navbar-default-link-disabled-bg:          transparent;
-
-// Navbar brand label
-@navbar-default-brand-color:               @navbar-default-link-color;
-@navbar-default-brand-hover-color:         @navbar-default-link-hover-color;
-@navbar-default-brand-hover-bg:            transparent;
-
-// Navbar toggle
-@navbar-default-toggle-hover-bg:           darken(@navbar-default-bg, 10%);
-@navbar-default-toggle-icon-bar-bg:        #fff;
-@navbar-default-toggle-border-color:       darken(@navbar-default-bg, 10%);
-
-
-// Inverted navbar
-// Reset inverted navbar basics
-@navbar-inverse-color:                      #fff;
-@navbar-inverse-bg:                         @brand-success;
-@navbar-inverse-border:                     transparent;
-
-// Inverted navbar links
-@navbar-inverse-link-color:                 #fff;
-@navbar-inverse-link-hover-color:           @brand-primary;
-@navbar-inverse-link-hover-bg:              transparent;
-@navbar-inverse-link-active-color:          #fff;
-@navbar-inverse-link-active-bg:             darken(@navbar-inverse-bg, 5%);
-@navbar-inverse-link-disabled-color:        #ccc;
-@navbar-inverse-link-disabled-bg:           transparent;
-
-// Inverted navbar brand label
-@navbar-inverse-brand-color:                @navbar-inverse-link-color;
-@navbar-inverse-brand-hover-color:          @navbar-inverse-link-hover-color;
-@navbar-inverse-brand-hover-bg:             transparent;
-
-// Inverted navbar toggle
-@navbar-inverse-toggle-hover-bg:            darken(@navbar-inverse-bg, 10%);
-@navbar-inverse-toggle-icon-bar-bg:         #fff;
-@navbar-inverse-toggle-border-color:        darken(@navbar-inverse-bg, 10%);
-
-
-//== Navs
-//
-//##
-
-//=== Shared nav styles
-@nav-link-padding:                          10px 15px;
-@nav-link-hover-bg:                         @gray-lighter;
-
-@nav-disabled-link-color:                   @gray-light;
-@nav-disabled-link-hover-color:             @gray-light;
-
-//== Tabs
-@nav-tabs-border-color:                     @gray-lighter;
-
-@nav-tabs-link-hover-border-color:          @gray-lighter;
-
-@nav-tabs-active-link-hover-bg:             @body-bg;
-@nav-tabs-active-link-hover-color:          @brand-primary;
-@nav-tabs-active-link-hover-border-color:   @gray-lighter;
-
-@nav-tabs-justified-link-border-color:            @gray-lighter;
-@nav-tabs-justified-active-link-border-color:     @body-bg;
-
-//== Pills
-@nav-pills-border-radius:                   @border-radius-base;
-@nav-pills-active-link-hover-bg:            @component-active-bg;
-@nav-pills-active-link-hover-color:         @component-active-color;
-
-
-//== Pagination
-//
-//##
-
-@pagination-color:                     #fff;
-@pagination-bg:                        @brand-success;
-@pagination-border:                    transparent;
-
-@pagination-hover-color:               #fff;
-@pagination-hover-bg:                  darken(@brand-success, 15%);
-@pagination-hover-border:              transparent;
-
-@pagination-active-color:              #fff;
-@pagination-active-bg:                 darken(@brand-success, 15%);
-@pagination-active-border:             transparent;
-
-@pagination-disabled-color:            @gray-lighter;
-@pagination-disabled-bg:               lighten(@brand-success, 15%);;
-@pagination-disabled-border:           transparent;
-
-
-//== Pager
-//
-//##
-
-@pager-bg:                             @pagination-bg;
-@pager-border:                         @pagination-border;
-@pager-border-radius:                  15px;
-
-@pager-hover-bg:                       @pagination-hover-bg;
-
-@pager-active-bg:                      @pagination-active-bg;
-@pager-active-color:                   @pagination-active-color;
-
-@pager-disabled-color:                 #fff;
-
-
-//== Jumbotron
-//
-//##
-
-@jumbotron-padding:              30px;
-@jumbotron-color:                inherit;
-@jumbotron-bg:                   @gray-lighter;
-@jumbotron-heading-color:        inherit;
-@jumbotron-font-size:            ceil((@font-size-base * 1.5));
-
-
-//== Form states and alerts
-//
-//## Define colors for form feedback states and, by default, alerts.
-
-@state-success-text:             #fff;
-@state-success-bg:               @brand-success;
-@state-success-border:           @brand-success;
-
-@state-info-text:                #fff;
-@state-info-bg:                  @brand-info;
-@state-info-border:              @brand-info;
-
-@state-warning-text:             #fff;
-@state-warning-bg:               @brand-warning;
-@state-warning-border:           @brand-warning;
-
-@state-danger-text:              #fff;
-@state-danger-bg:                @brand-danger;
-@state-danger-border:            @brand-danger;
-
-
-//== Tooltips
-//
-//##
-
-//** Tooltip max width
-@tooltip-max-width:           200px;
-//** Tooltip text color
-@tooltip-color:               #fff;
-//** Tooltip background color
-@tooltip-bg:                  rgba(0,0,0,.9);
-@tooltip-opacity:             .9;
-
-//** Tooltip arrow width
-@tooltip-arrow-width:         5px;
-//** Tooltip arrow color
-@tooltip-arrow-color:         @tooltip-bg;
-
-
-//== Popovers
-//
-//##
-
-//** Popover body background color
-@popover-bg:                          #fff;
-//** Popover maximum width
-@popover-max-width:                   276px;
-//** Popover border color
-@popover-border-color:                rgba(0,0,0,.2);
-//** Popover fallback border color
-@popover-fallback-border-color:       #ccc;
-
-//** Popover title background color
-@popover-title-bg:                    darken(@popover-bg, 3%);
-
-//** Popover arrow width
-@popover-arrow-width:                 10px;
-//** Popover arrow color
-@popover-arrow-color:                 @popover-bg;
-
-//** Popover outer arrow width
-@popover-arrow-outer-width:           (@popover-arrow-width + 1);
-//** Popover outer arrow color
-@popover-arrow-outer-color:           fadein(@popover-border-color, 5%);
-//** Popover outer arrow fallback color
-@popover-arrow-outer-fallback-color:  darken(@popover-fallback-border-color, 20%);
-
-
-//== Labels
-//
-//##
-
-//** Default label background color
-@label-default-bg:            @btn-default-bg;
-//** Primary label background color
-@label-primary-bg:            @brand-primary;
-//** Success label background color
-@label-success-bg:            @brand-success;
-//** Info label background color
-@label-info-bg:               @brand-info;
-//** Warning label background color
-@label-warning-bg:            @brand-warning;
-//** Danger label background color
-@label-danger-bg:             @brand-danger;
-
-//** Default label text color
-@label-color:                 #fff;
-//** Default text color of a linked label
-@label-link-hover-color:      #fff;
-
-
-//== Modals
-//
-//##
-
-//** Padding applied to the modal body
-@modal-inner-padding:         20px;
-
-//** Padding applied to the modal title
-@modal-title-padding:         15px;
-//** Modal title line-height
-@modal-title-line-height:     @line-height-base;
-
-//** Background color of modal content area
-@modal-content-bg:                             #fff;
-//** Modal content border color
-@modal-content-border-color:                   rgba(0,0,0,.2);
-//** Modal content border color **for IE8**
-@modal-content-fallback-border-color:          #999;
-
-//** Modal backdrop background color
-@modal-backdrop-bg:           #000;
-//** Modal backdrop opacity
-@modal-backdrop-opacity:      .5;
-//** Modal header border color
-@modal-header-border-color:   #e5e5e5;
-//** Modal footer border color
-@modal-footer-border-color:   @modal-header-border-color;
-
-@modal-lg:                    900px;
-@modal-md:                    600px;
-@modal-sm:                    300px;
-
-
-//== Alerts
-//
-//## Define alert colors, border radius, and padding.
-
-@alert-padding:               15px;
-@alert-border-radius:         @border-radius-base;
-@alert-link-font-weight:      bold;
-
-@alert-success-bg:            @state-success-bg;
-@alert-success-text:          @state-success-text;
-@alert-success-border:        @state-success-border;
-
-@alert-info-bg:               @state-info-bg;
-@alert-info-text:             @state-info-text;
-@alert-info-border:           @state-info-border;
-
-@alert-warning-bg:            @state-warning-bg;
-@alert-warning-text:          @state-warning-text;
-@alert-warning-border:        @state-warning-border;
-
-@alert-danger-bg:             @state-danger-bg;
-@alert-danger-text:           @state-danger-text;
-@alert-danger-border:         @state-danger-border;
-
-
-//== Progress bars
-//
-//##
-
-//** Background color of the whole progress component
-@progress-bg:                 @gray-lighter;
-//** Progress bar text color
-@progress-bar-color:          #fff;
-//** Variable for setting rounded corners on progress bar.
-@progress-border-radius:      @border-radius-base;
-
-//** Default progress bar color
-@progress-bar-bg:             @brand-primary;
-//** Success progress bar color
-@progress-bar-success-bg:     @brand-success;
-//** Warning progress bar color
-@progress-bar-warning-bg:     @brand-warning;
-//** Danger progress bar color
-@progress-bar-danger-bg:      @brand-danger;
-//** Info progress bar color
-@progress-bar-info-bg:        @brand-info;
-
-
-//== List group
-//
-//##
-
-//** Background color on `.list-group-item`
-@list-group-bg:                 #fff;
-//** `.list-group-item` border color
-@list-group-border:             @gray-lighter;
-//** List group border radius
-@list-group-border-radius:      @border-radius-base;
-
-//** Background color of single list items on hover
-@list-group-hover-bg:           @gray-lighter;
-//** Text color of active list items
-@list-group-active-color:       @component-active-color;
-//** Background color of active list items
-@list-group-active-bg:          @component-active-bg;
-//** Border color of active list elements
-@list-group-active-border:      @list-group-active-bg;
-//** Text color for content within active list items
-@list-group-active-text-color:  lighten(@list-group-active-bg, 40%);
-
-//** Text color of disabled list items
-@list-group-disabled-color:      @gray-light;
-//** Background color of disabled list items
-@list-group-disabled-bg:         @gray-lighter;
-//** Text color for content within disabled list items
-@list-group-disabled-text-color: @list-group-disabled-color;
-
-@list-group-link-color:         #555;
-@list-group-link-hover-color:   @list-group-link-color;
-@list-group-link-heading-color: #333;
-
-
-//== Panels
-//
-//##
-
-@panel-bg:                    #fff;
-@panel-body-padding:          15px;
-@panel-heading-padding:       10px 15px;
-@panel-footer-padding:        @panel-heading-padding;
-@panel-border-radius:         @border-radius-base;
-
-//** Border color for elements within panels
-@panel-inner-border:          @gray-lighter;
-@panel-footer-bg:             @gray-lighter;
-
-@panel-default-text:          @text-color;
-@panel-default-border:        @gray-lighter;
-@panel-default-heading-bg:    @gray-lighter;
-
-@panel-primary-text:          #fff;
-@panel-primary-border:        @brand-primary;
-@panel-primary-heading-bg:    @brand-primary;
-
-@panel-success-text:          @state-success-text;
-@panel-success-border:        @state-success-border;
-@panel-success-heading-bg:    @state-success-bg;
-
-@panel-info-text:             @state-info-text;
-@panel-info-border:           @state-info-border;
-@panel-info-heading-bg:       @state-info-bg;
-
-@panel-warning-text:          @state-warning-text;
-@panel-warning-border:        @state-warning-border;
-@panel-warning-heading-bg:    @state-warning-bg;
-
-@panel-danger-text:           @state-danger-text;
-@panel-danger-border:         @state-danger-border;
-@panel-danger-heading-bg:     @state-danger-bg;
-
-
-//== Thumbnails
-//
-//##
-
-//** Padding around the thumbnail image
-@thumbnail-padding:           4px;
-//** Thumbnail background color
-@thumbnail-bg:                @body-bg;
-//** Thumbnail border color
-@thumbnail-border:            @gray-lighter;
-//** Thumbnail border radius
-@thumbnail-border-radius:     @border-radius-base;
-
-//** Custom text color for thumbnail captions
-@thumbnail-caption-color:     @text-color;
-//** Padding around the thumbnail caption
-@thumbnail-caption-padding:   9px;
-
-
-//== Wells
-//
-//##
-
-@well-bg:                     @gray-lighter;
-@well-border:                 transparent;
-
-
-//== Badges
-//
-//##
-
-@badge-color:                 #fff;
-//** Linked badge text color on hover
-@badge-link-hover-color:      #fff;
-@badge-bg:                    @brand-primary;
-
-//** Badge text color in active nav link
-@badge-active-color:          @brand-primary;
-//** Badge background color in active nav link
-@badge-active-bg:             #fff;
-
-@badge-font-weight:           bold;
-@badge-line-height:           1;
-@badge-border-radius:         10px;
-
-
-//== Breadcrumbs
-//
-//##
-
-@breadcrumb-padding-vertical:   8px;
-@breadcrumb-padding-horizontal: 15px;
-//** Breadcrumb background color
-@breadcrumb-bg:                 @gray-lighter;
-//** Breadcrumb text color
-@breadcrumb-color:              #ccc;
-//** Text color of current page in the breadcrumb
-@breadcrumb-active-color:       @gray;
-//** Textual separator for between breadcrumb elements
-@breadcrumb-separator:          "/";
-
-
-//== Carousel
-//
-//##
-
-@carousel-text-shadow:                        0 1px 2px rgba(0,0,0,.6);
-
-@carousel-control-color:                      #fff;
-@carousel-control-width:                      15%;
-@carousel-control-opacity:                    .5;
-@carousel-control-font-size:                  20px;
-
-@carousel-indicator-active-bg:                #fff;
-@carousel-indicator-border-color:             #fff;
-
-@carousel-caption-color:                      #fff;
-
-
-//== Close
-//
-//##
-
-@close-font-weight:           bold;
-@close-color:                 #000;
-@close-text-shadow:           none;
-
-
-//== Code
-//
-//##
-
-@code-color:                  #c7254e;
-@code-bg:                     #f9f2f4;
-
-@kbd-color:                   #fff;
-@kbd-bg:                      #333;
-
-@pre-bg:                      @gray-lighter;
-@pre-color:                   @gray-dark;
-@pre-border-color:            #ccc;
-@pre-scrollable-max-height:   340px;
-
-
-//== Type
-//
-//##
-
-//** Horizontal offset for forms and lists.
-@component-offset-horizontal: 180px;
-//** Text muted color
-@text-muted:                  @gray-light;
-//** Abbreviations and acronyms border color
-@abbr-border-color:           @gray-light;
-//** Headings small color
-@headings-small-color:        @gray-light;
-//** Blockquote small color
-@blockquote-small-color:      @gray-light;
-//** Blockquote font size
-@blockquote-font-size:        (@font-size-base * 1.25);
-//** Blockquote border color
-@blockquote-border-color:     @gray-lighter;
-//** Page header border color
-@page-header-border-color:    transparent;
-//** Width of horizontal description list titles
-@dl-horizontal-offset:        @component-offset-horizontal;
-//** Horizontal line color.
-@hr-border:                   @gray-lighter;
-
-// Flatly 3.3.0
-// Bootswatch
-// -----------------------------------------------------
-
-@import url("https://fonts.googleapis.com/css?family=Lato:400,700,400italic");
-
-// Navbar =====================================================================
-
-.navbar {
-  border-width: 0;
-
-  &-default {
-
-    .badge {
-      background-color: #fff;
-      color: @navbar-default-bg;
-    }
-  }
-
-  &-inverse {
-
-    .badge {
-      background-color: #fff;
-      color: @navbar-inverse-bg;
-    }
-  }
-
-  &-brand {
-    padding: 18.5px 15px 20.5px;
-  }
-}
-
-// Buttons ====================================================================
-
-.btn:active {
-  .box-shadow(none);
-}
-
-.btn-group.open .dropdown-toggle {
-  .box-shadow(none);
-}
-
-// Typography =================================================================
-
-.text-primary,
-.text-primary:hover {
-  color: @brand-primary;
-}
-
-.text-success,
-.text-success:hover {
-  color: @brand-success;
-}
-
-.text-danger,
-.text-danger:hover {
-  color: @brand-danger;
-}
-
-.text-warning,
-.text-warning:hover {
-  color: @brand-warning;
-}
-
-.text-info,
-.text-info:hover {
-  color: @brand-info;
-}
-
-// Tables =====================================================================
-
-table,
-.table {
-
-  a:not(.btn) {
-    text-decoration: underline;
-  }
-
-  .dropdown-menu a {
-    text-decoration: none;
-  }
-
-  .success,
-  .warning,
-  .danger,
-  .info {
-    color: #fff;
-
-    a {
-      color: #fff;
-    }
-  }
-
-  > thead > tr > th,
-  > tbody > tr > th,
-  > tfoot > tr > th,
-  > thead > tr > td,
-  > tbody > tr > td,
-  > tfoot > tr > td {
-    border: none;
-  }
-
-  &-bordered > thead > tr > th,
-  &-bordered > tbody > tr > th,
-  &-bordered > tfoot > tr > th,
-  &-bordered > thead > tr > td,
-  &-bordered > tbody > tr > td,
-  &-bordered > tfoot > tr > td {
-    border: 1px solid @table-border-color;
-  }
-}
-
-// Forms ======================================================================
-
-.form-control,
-input, {
-  border-width: 2px;
-  .box-shadow(none);
-
-  &:focus {
-    .box-shadow(none);
-  }
-}
-
-.has-warning {
-  .help-block,
-  .control-label,
-  .radio,
-  .checkbox,
-  .radio-inline,
-  .checkbox-inline,
-  .form-control-feedback {
-    color: @brand-warning;
-  }
-
-  .form-control,
-  .form-control:focus {
-    border: 2px solid @brand-warning;
-  }
-
-  .input-group-addon {
-    border-color: @brand-warning;
-  }
-}
-
-.has-error {
-  .help-block,
-  .control-label,
-  .radio,
-  .checkbox,
-  .radio-inline,
-  .checkbox-inline,
-  .form-control-feedback {
-    color: @brand-danger;
-  }
-
-  .form-control,
-  .form-control:focus {
-    border: 2px solid @brand-danger;
-  }
-
-  .input-group-addon {
-    border-color: @brand-danger;
-  }
-}
-
-.has-success {
-  .help-block,
-  .control-label,
-  .radio,
-  .checkbox,
-  .radio-inline,
-  .checkbox-inline,
-  .form-control-feedback {
-    color: @brand-success;
-  }
-
-  .form-control,
-  .form-control:focus {
-    border: 2px solid @brand-success;
-  }
-
-  .input-group-addon {
-    border-color: @brand-success;
-  }
-}
-
-// Navs =======================================================================
-
-.nav {
-  .open > a,
-  .open > a:hover,
-  .open > a:focus {
-    border-color: transparent;
-  }
-}
-
-.pager {
-  a,
-  a:hover {
-    color: #fff;
-  }
-
-  .disabled {
-    &>a,
-    &>a:hover,
-    &>a:focus,
-    &>span {
-      background-color: @pagination-disabled-bg;
-    }
-  }
-}
-
-// Indicators =================================================================
-
-.close {
-  color: #fff;
-  text-decoration: none;
-  opacity: 0.4;
-
-  &:hover,
-  &:focus {
-    color: #fff;
-    opacity: 1;
-  }
-}
-
-.alert {
-
-  .alert-link {
-    color: #fff;
-    text-decoration: underline;
-  }
-}
-
-// Progress bars ==============================================================
-
-.progress {
-  height: 10px;
-  .box-shadow(none);
-  .progress-bar {
-    font-size: 10px;
-    line-height: 10px;
-  }
-}
-
-// Containers =================================================================
-
-.well {
-  .box-shadow(none);
-}
-
-a.list-group-item {
-
-  &.active,
-  &.active:hover,
-  &.active:focus {
-    border-color: @list-group-border;
-  }
-
-  &-success {
-    &.active {
-      background-color: @state-success-bg;
-    }
-
-    &.active:hover,
-    &.active:focus {
-      background-color: darken(@state-success-bg, 5%);
-    }
-  }
-
-  &-warning {
-    &.active {
-      background-color: @state-warning-bg;
-    }
-
-    &.active:hover,
-    &.active:focus {
-      background-color: darken(@state-warning-bg, 5%);
-    }
-  }
-
-  &-danger {
-    &.active {
-      background-color: @state-danger-bg;
-    }
-
-    &.active:hover,
-    &.active:focus {
-      background-color: darken(@state-danger-bg, 5%);
-    }
-  }
-}
-
-.panel {
-  &-default {
-    .close {
-      color: @text-color;
-    }
-  }
-}
-
-.modal {
-  .close {
-    color: @text-color;
-  }
-}
-
-.popover {
-  color: @text-color;
-}
diff --git a/public/css/themes/modern.less b/public/css/themes/modern.less
deleted file mode 100644
index f9e6d8b25f..0000000000
--- a/public/css/themes/modern.less
+++ /dev/null
@@ -1,151 +0,0 @@
-@import url("http://fonts.googleapis.com/css?family=Montserrat:400,700");
-@import url("http://fonts.googleapis.com/css?family=Raleway:400,300");
-
-// Colors
-// -------------------------
-
-@gray-lighter: #f5f5f5;
-@gray-light: #e0e0e0;
-@gray: #737373;
-@gray-dark: #404040;
-@gray-darker: #121212;
-
-@brand-primary: #2ac5ee;
-@brand-success: #0f9d58;
-@brand-warning: #f4b400;
-@brand-danger: #d80017;
-@brand-info: #5bc0dd;
-
-// Footer
-// -------------------------
-
-footer {
-  color: #fff;
-  background-color: @gray-darker;
-
-  a:hover {
-    color: #fff;
-  }
-}
-
-// Typography
-// -------------------------
-
-@font-size-base: 13px;
-@font-family-base: 'Montserrat', sans-serif;
-@headings-font-family: 'Montserrat', sans-serif;
-@headings-font-weight: 700;
-@text-color: @gray-darker;
-@link-color: @brand-primary;
-@link-hover-color: @gray-darker;
-
-a {
-  transition: all .4s cubic-bezier(.24,.45,.46,.92);
-
-  &:hover {
-    text-decoration: none;
-  }
-}
-
-ul,
-p {
-  font-family: 'Raleway', sans-serif;
-  font-size: 14px;
-  font-weight: 300;
-}
-
-.text-danger {
-  color: @brand-danger;
-}
-
-// Dropdowns
-// -------------------------
-
-@dropdown-link-color: @gray-darker;
-
-.dropdown-menu > li > a {
-  font-weight: 300;
-}
-
-// Buttons
-// -------------------------
-
-@btn-default-bg: @gray-lighter;
-@btn-default-color: @gray-darker;
-
-.btn {
-  padding: 10px 16px;
-  border: 0;
-  border-radius: 3px;
-  transition: all .4s cubic-bezier(.24,.45,.46,.92);
-
-  &:hover {
-    color: #fff;
-    background-color: @gray-darker;
-  }
-}
-
-.btn-social {
-  padding: 10px 50px;
-
-  :first-child {
-    width: 38px;
-    line-height: 38px;
-  }
-}
-
-.btn-link {
-
-  &:hover {
-    color: @gray-darker;
-    text-decoration: none;
-    background-color: transparent;
-  }
-}
-
-// Forms
-// -------------------------
-
-@input-border-radius: 2px;
-@input-border-focus: #999;
-@input-border: #f0f0f0;
-@input-color: #444;
-@input-color-placeholder: #999;
-@input-height-base: 44px;
-
-// Form states and alerts
-// -------------------------
-
-@state-success-bg: @brand-success;
-@state-info-bg: @brand-primary;
-@state-warning-bg: @brand-warning;
-@state-danger-bg: @brand-danger;
-
-.alert {
-  font-family: 'Raleway', sans-serif;
-  color: #fff;
-}
-
-// Navbar
-// -------------------------
-
-@navbar-height: 50px;
-@navbar-default-bg: rgba(255,255,255,0.95);
-@navbar-default-link-color: @gray-darker;
-@navbar-default-link-active-color: @brand-primary;
-@navbar-default-link-active-bg: transparent;
-@navbar-default-link-hover-color: @brand-primary;
-@navbar-default-link-hover-bg: transparent;
-@navbar-default-brand-color: @gray-darker;
-@navbar-default-brand-hover-color: @brand-primary;
-
-.navbar-default {
-  border: 0;
-  box-shadow: 0 1px 5px rgba(0,0,0,.15);
-
-  .navbar-nav {
-    > li > a {
-      transition: color .4s cubic-bezier(.24,.45,.46,.92);
-    }
-  }
-}
\ No newline at end of file
diff --git a/public/favicon.ico b/public/favicon.ico
index 3ec62dd50c2c8d41f224547395abfc9d7b1831e6..60054e19920e2111dc3c18b27e97bdc04da2e146 100644
GIT binary patch
literal 1150
zcmd5+&ubG=5T4yl(|{>#JOm0o?4hOh*53LL6nZP9VAn*qb(3ti!5B!IQ0ztQp@k4y
zFJ^5qh{0dmV^IShYC-VgMetB5^j1(W{t2J+-X<|^^`aLOX5VJs%s1bBGlWRtudWk6
z4RS+7aU#0H#xC{*em;o!?((I;38rU4U)B-N#WB4;Eq$WS+}0dQFH5qf&`IEoR7Oy5*FhHNa{h36
z7{}j^VN?xF-krqW$35t`bZVzTT8PC3ns@Rt?nN)a1R?mobjM9-;lYjl(a8
zIQ?;of!u(^pIDo~#;c8A`8+S7dE=mgNIDYek|NC1W*|QMpZ4KCaY3xF&@FL4f#UOG
zkH74J`>Og?*iBo`O=zpx8cx2SV75LhV+AiU|JH}r8U2KIVrRmcFuZpj#@5C_TwnB*
z+)09NQox;k#TP|5j~$68d++X1mbr0U)QKW`D=P5>rkJI~@5IFlF=$IXZxP?k?VNId
l)4UU6ejVSL8p6kSf85`5cwLPCv!6@9|DWD_e*}N`@)N60sYL()

literal 7406
zcmeHLOQ;?-6h1RaCYem;k<2U=E*7e^3boo|kzy6Rmm*@7yC{NfMSP*Oq97=R3L?@)
z1zl7q;^MmSv9${kicl03D+)dqDiw4g!u7E#Zp!r}nM~%t_rJXh7cJ!v%;cPW-+AQZ
zpZu8tNPs}o0QLf&U;sM-U~bOq?+JjnactYRUatV=@wx#yXaY!ccGy`VwqCyGmVMCO
zxEJo(|0F#3((ADQ(dS{u!a-R6>mS&(eF3Vuop9#t=U}!hz>yb^!y`wIL3{lk*mC=Q
z@Wof(!W(bB3ooBI1!mJOxbC*a=zk{7Dln_S6<>kGx6<@&m1?Xm2h^Z0ArY=|Ym&%T
zGgV+yN;$nyWl9yLDc4Mxq;0q`s^X~-X(5bc+L)?N(}o)>I-TcvT~}5tCGw4+Sxyp3
zixR^X%9L%bFi^uJ>KIQl1yU-7s?7)RhC*w#QFG`>pDO;}1+x3>9A10yviq}Z)OYUQ
z@3Yt5bQhfa`a3xM!mDt{zQ^G7$7kW^Uw(sEUOVOX*aJ@;g^L$2!q(l3@X4oN!t$Sg
z!_xa7Lc4V@>^txb%?Xf4`dOJ%
zUxVridXteI=?M*Jj8p6OOh<~W(cCDpe5V4aM-+p#`suxVtFuJ(TPtG_IwJ*VI+
zA{@eNIH%;Ogw~%^R#sNr`{0R_OK{=BzwT_ZdDs1L7~lQuJK)j>r{UP~lQ4JlJpBI0
zGTeIigYeV&U*XON9*1u8ZaDPxQCNKVS@`J8XK?PD@7=Q{S5=O
zalu^-$LCei#uR0qDoibpvy^QS6wl8f9lX_|bYK?iNYf#_&qsg`e42rA4@@mu)=#t6
zAbQd9aY7+pCymGO;0L*PQe(c}hhJaL$}*%+RRK1ix?;Ky#1pQ{FhA>`t2kZD2cxJa
zX$qxHh$CTeSO@;_jLR06xJBnJ20}j4Bj3>{{CCtHY8p+fnRN7l;OWjhl%nex1|XTS
zKO|9G@94iGcD_9w2Mie&yrT^PQbK76(Vm`gw?2{1<9gT$y01bxD^lm&-t78BdWh$j
z#8w+K(v4ujy(&sJqT}Z-L&5^vlraE)ehc&j!>1GUgg4=tuyl?kGLrY+F*<&{3ra{4
zp9#AJt{~`-WCMXS1Nor!e0&~B7U)$$;#4^kV20F!dkk8V%$HlHLMPE-3T+j&Xn^}k
sRHDT2bK(@b{gJSNH5S;~WIQ|+WXwiCQ@u}eX4=(K0lN*%+U2Xje*{S6K>z>%

diff --git a/public/favicons.html b/public/favicons.html
deleted file mode 100644
index ba348ed7db..0000000000
--- a/public/favicons.html
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/public/images/freeCodeCamp-puck.svg b/public/images/freeCodeCamp-puck.svg
new file mode 100644
index 0000000000..f2572ed38b
--- /dev/null
+++ b/public/images/freeCodeCamp-puck.svg
@@ -0,0 +1,37 @@
+
+
+
+
+Created by potrace 1.15, written by Peter Selinger 2001-2017
+
+
+
+
+
diff --git a/public/images/freecodecamp_logo.svg b/public/images/freecodecamp_logo.svg
deleted file mode 100644
index 2c24007979..0000000000
--- a/public/images/freecodecamp_logo.svg
+++ /dev/null
@@ -1,199 +0,0 @@
-
-
-
-image/svg+xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-	
-	
-
-
-
-
\ No newline at end of file

From 8b0cec8678fd0183d78cfb2eddfb90eb89b7d5d0 Mon Sep 17 00:00:00 2001
From: Mrugesh Mohapatra 
Date: Fri, 18 May 2018 23:09:27 +0530
Subject: [PATCH 04/12] fix: Update view copy

---
 server/views/account/email-signin.jade | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/server/views/account/email-signin.jade b/server/views/account/email-signin.jade
index fee55113fd..34602473aa 100644
--- a/server/views/account/email-signin.jade
+++ b/server/views/account/email-signin.jade
@@ -18,6 +18,12 @@ block content
                   .form-group
                       input.input-lg.form-control(type='email', name='email', id='email', placeholder='Enter your email address', autofocus=true, required, oninvalid="this.setCustomValidity('Enter your email address')", oninput="setCustomValidity('')")
                       .button-spacer
+                      div.checkbox
+                        label
+                          input(type='checkbox', name='quincy-emails', checked=false)
+                          span.cr
+                            i.cr-icon.fa.fa-check 
+                          |  I want weekly emails from Quincy, freeCodeCamp.org's founder.
                       div.checkbox
                         label
                           input#terms-privacy(type='checkbox', name='terms-privacy', checked=false)
@@ -27,13 +33,7 @@ block content
                           a(href="/terms" target="_blank") Terms of Service 
                           | and 
                           a(href="/privacy" target="_blank") Privacy Policy 
-                          | (required)
-                      div.checkbox
-                        label
-                          input(type='checkbox', name='quincy-emails', checked=false)
-                          span.cr
-                            i.cr-icon.fa.fa-check 
-                          |  I want weekly emails from Quincy (freeCodeCamp.org's founder)
+                          | (required).
                       .button-spacer
                       button#magic-btn.btn.btn-primary.btn-lg.btn-block(type='submit')
                         | Get a sign in link

From c2a0443486699d2ef68521fe743a255958609eb6 Mon Sep 17 00:00:00 2001
From: Mrugesh Mohapatra 
Date: Sat, 19 May 2018 01:41:44 +0530
Subject: [PATCH 05/12] fix: report error only when enabled

---
 server/middlewares/error-reporter.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/middlewares/error-reporter.js b/server/middlewares/error-reporter.js
index 5b3aaba86b..2eb8455e2c 100644
--- a/server/middlewares/error-reporter.js
+++ b/server/middlewares/error-reporter.js
@@ -10,7 +10,7 @@ const log = debug('fcc:middlewares:error-reporter');
 
 const isOpbeatDisabled = !opbeat.appId;
 export default function errrorReporter() {
-  if (process.env.NODE_ENV !== 'production') {
+  if (process.env.NODE_ENV !== 'production' && process.env.ERROR_REPORTER) {
     return (err, req, res, next) => {
       if (isHandledError(err)) {
         // log out user messages in development

From 53b362e5bd08ed0d4fd2f52aab4197cb3e0451b6 Mon Sep 17 00:00:00 2001
From: Mrugesh Mohapatra 
Date: Sat, 19 May 2018 14:09:27 +0530
Subject: [PATCH 06/12] fix(views): Adjust routes and views

---
 server/boot/authentication.js               |  12 +-
 server/passport-providers.js                |   1 +
 server/views/account/deprecated-signin.jade |  50 ++++---
 server/views/account/deprecated-signup.jade |  15 +++
 server/views/account/email-signin.jade      | 140 --------------------
 5 files changed, 52 insertions(+), 166 deletions(-)
 create mode 100644 server/views/account/deprecated-signup.jade
 delete mode 100644 server/views/account/email-signin.jade

diff --git a/server/boot/authentication.js b/server/boot/authentication.js
index 38ceaa63de..a3a9b05ad5 100644
--- a/server/boot/authentication.js
+++ b/server/boot/authentication.js
@@ -29,23 +29,25 @@ module.exports = function enableAuthentication(app) {
   const api = app.loopback.Router();
   const { AuthToken, User } = app.models;
 
-  router.get('/signup', (req, res) => res.redirect(301, '/login'));
   router.get('/email-signin', (req, res) => res.redirect(301, '/login'));
   router.get('/signin', (req, res) => res.redirect(301, '/login'));
   router.get('/signout', (req, res) => res.redirect(301, '/logout'));
+  router.get('/signup', (req, res) => res.redirect(301, '/deprecated-signup'));
 
-  function getEmailSignin(req, res) {
+  function getLegacySignUp(req, res) {
     if (isSignUpDisabled) {
       return res.render('account/beta', {
         title: 'New sign ups are disabled'
       });
     }
-    return res.render('account/email-signin', {
+    return res.render('account/deprecated-signup', {
       title: 'Sign in to freeCodeCamp using your Email Address'
     });
   }
-
-  router.get('/login', ifUserRedirect, getEmailSignin);
+  router.get('/deprecated-signup', ifUserRedirect, getLegacySignUp);
+  router.get('/login',
+  ifUserRedirect,
+  (req, res) => res.redirect(301, '/auth/auth0'));
 
   router.get('/logout', (req, res) => {
     req.logout();
diff --git a/server/passport-providers.js b/server/passport-providers.js
index 59816d6c60..7ee12a9ceb 100644
--- a/server/passport-providers.js
+++ b/server/passport-providers.js
@@ -172,6 +172,7 @@ export default {
     clientID: process.env.AUTH0_CLIENT_ID,
     clientSecret: process.env.AUTH0_CLIENT_SECRET,
     domain: process.env.AUTH0_DOMAIN,
+    cookieDomain: 'freeCodeCamp.org',
     callbackURL: '/auth/auth0/callback',
     authPath: '/auth/auth0',
     callbackPath: '/auth/auth0/callback',
diff --git a/server/views/account/deprecated-signin.jade b/server/views/account/deprecated-signin.jade
index 0cb67ef558..9d51f4f018 100644
--- a/server/views/account/deprecated-signin.jade
+++ b/server/views/account/deprecated-signin.jade
@@ -1,26 +1,34 @@
 extends ../layout
 block content
-    .text-center
-        h2 If you originally signed up using one of these methods, you can sign in and add your email address to your account:
-        br
-        a.btn.btn-lg.btn-block.btn-social.btn-github(href='/auth/github')
-            i.fa.fa-github
-            | Sign in with GitHub
-        a.btn.btn-lg.btn-block.btn-social.btn-facebook(href='/auth/facebook')
-            i.fa.fa-facebook
-            | Sign in with Facebook
-        a.btn.btn-lg.btn-block.btn-social.btn-google(href='/auth/google')
-            i.fa.fa-google
-            | Sign in with Google
-        a.btn.btn-lg.btn-block.btn-social.btn-linkedin(href='/auth/linkedin')
-            i.fa.fa-linkedin
-            | Sign in with LinkedIn
-        a.btn.btn-lg.btn-block.btn-social.btn-twitter(href='/auth/twitter')
-            i.fa.fa-twitter
-            | Sign in with Twitter
-        br
-        p
-          a(href="/signin") Or click here to go back.
+  .container
+    .col-xs-12
+      .row
+        .text-center
+            h3 Sign in with one of these options if you used them as your original sign up methods :
+            br
+            a.btn.btn-lg.btn-block.btn-social.btn-github(href='/auth/github')
+                i.fa.fa-github
+                | Sign in with GitHub
+            a.btn.btn-lg.btn-block.btn-social.btn-facebook(href='/auth/facebook')
+                i.fa.fa-facebook
+                | Sign in with Facebook
+            a.btn.btn-lg.btn-block.btn-social.btn-google(href='/auth/google')
+                i.fa.fa-google
+                | Sign in with Google
+            a.btn.btn-lg.btn-block.btn-social.btn-linkedin(href='/auth/linkedin')
+                i.fa.fa-linkedin
+                | Sign in with LinkedIn
+            a.btn.btn-lg.btn-block.btn-social.btn-twitter(href='/auth/twitter')
+                i.fa.fa-twitter
+                | Sign in with Twitter
+            br
+            h3
+             | We are unable to create new accounts using these methods
+            h4 If you haven't updated your email with us, you should do that as soon as possible,
+             | after you login here, to avoid losing access to your account.
+            br
+            p
+              a(href="/signin") Or click here to go back.
 
     script.
       $(document).ready(function() {
diff --git a/server/views/account/deprecated-signup.jade b/server/views/account/deprecated-signup.jade
new file mode 100644
index 0000000000..8d15ddffba
--- /dev/null
+++ b/server/views/account/deprecated-signup.jade
@@ -0,0 +1,15 @@
+extends ../layout
+block content
+    .container
+      .col-xs-12
+        .row
+          .text-center
+            h2 Sign up (or sign in with your existing account)
+            br
+            a.btn.btn-lg.btn-primary(href='/auth/auth0')
+              | Get a link on your email
+        .row
+          .text-center
+            br
+            a(href="/deprecated-signin")
+              | Continute with old sign in methods
diff --git a/server/views/account/email-signin.jade b/server/views/account/email-signin.jade
deleted file mode 100644
index 34602473aa..0000000000
--- a/server/views/account/email-signin.jade
+++ /dev/null
@@ -1,140 +0,0 @@
-extends ../layout
-block content
-    .container
-      .col-xs-12
-        .row
-          .col-sm-6.col-sm-offset-3.flashMessage.negative-30
-                #flash-board.alert.fade.in(style='display: none;')
-                    button.close(type='button', data-dismiss='alert')
-                        span.ion-close-circled#flash-close
-                    #flash-content
-        .row
-          .text-center
-            h2 Sign up (or sign in with your existing account)
-              .button-spacer
-          .col-sm-6.col-sm-offset-3
-              form(method='POST', action='/passwordless-auth')
-                  input(type='hidden', name='_csrf', value=_csrf)
-                  .form-group
-                      input.input-lg.form-control(type='email', name='email', id='email', placeholder='Enter your email address', autofocus=true, required, oninvalid="this.setCustomValidity('Enter your email address')", oninput="setCustomValidity('')")
-                      .button-spacer
-                      div.checkbox
-                        label
-                          input(type='checkbox', name='quincy-emails', checked=false)
-                          span.cr
-                            i.cr-icon.fa.fa-check 
-                          |  I want weekly emails from Quincy, freeCodeCamp.org's founder.
-                      div.checkbox
-                        label
-                          input#terms-privacy(type='checkbox', name='terms-privacy', checked=false)
-                          span.cr
-                            i.cr-icon.fa.fa-check 
-                          | I accept the 
-                          a(href="/terms" target="_blank") Terms of Service 
-                          | and 
-                          a(href="/privacy" target="_blank") Privacy Policy 
-                          | (required).
-                      .button-spacer
-                      button#magic-btn.btn.btn-primary.btn-lg.btn-block(type='submit')
-                        | Get a sign in link
-        .row
-          .col-sm-6.col-sm-offset-3
-              br
-              p.text-center
-                br
-                br
-                a(href="/deprecated-signin") Try old sign in methods
-
-        script.
-          $(document).ready(function() {
-            function disableMagicButton (isDisabled, isLaunched) {
-                if (isDisabled) {
-                  $('#magic-btn')
-                    .prop('disabled', true)
-                    .html('OK - we\'re sending you an email. Open it and click sign in link.');
-                } else {
-                  $('#magic-btn')
-                    .prop('disabled', true)
-                    .html('If you didn\'t get the email, check your spam folder, or reload the page to try again.');
-                }
-            }
-            
-            function disableMagicButtonForAgreement (isLaunched) {
-              if(isLaunched) {
-                $('#magic-btn')
-                  .prop('disabled', true)
-                  .html('Get a sign in link');
-                return;
-              } else {
-              $('#magic-btn')
-                .prop('disabled', false)
-                .html('Get a sign in link');
-              }
-            }
-            
-            disableMagicButtonForAgreement(true);
-            
-            $('#terms-privacy').click(function(){
-              if(this.checked) {
-                disableMagicButtonForAgreement(false);
-              } else {
-                disableMagicButtonForAgreement(true);
-              }
-            });
-            
-            $('form').submit(function(event){
-              event.preventDefault();
-              $('#flash-board').hide();
-              disableMagicButton(true);
-              var $form = $(event.target);
-              $.ajax({
-                type        : 'POST',
-                url         : $form.attr('action'),
-                data        : $form.serialize(),
-                dataType    : 'json',
-                encode      : true,
-                xhrFields   : { withCredentials:  true }
-              })
-              .fail(error => {
-                if (error.responseText){
-                  var data = JSON.parse(error.responseText);
-                  if(data.error && data.error.message) {
-                    $('#flash-content').html(data.error.message);
-                    $('#flash-board')
-                      .removeClass('alert-success')
-                      .addClass('alert-info')
-                      .slideDown(400)
-                      .delay(800)
-                      .fadeIn();
-                    disableMagicButton(false);
-                  }
-                }
-              })
-              .done(data => {
-                if(data && data.message) {
-                  var alertType = 'alert-';
-                  switch (data.type) {
-                    case 'errors': {
-                      alertType += 'danger';
-                      break
-                    }
-                    case 'success': {
-                      alertType += 'success';
-                      break
-                    }
-                    default: {
-                      alertType += 'info';
-                    }
-                  }
-                  $('#flash-content').html(data.message);
-                  $('#flash-board')
-                    .removeClass('alert-info alert-success alert-danger')
-                    .addClass(alertType)
-                    .slideDown(400)
-                    .delay(800)
-                    .fadeIn();
-                  disableMagicButton(false);
-                }
-              });
-            });
-          });

From c58971866ad55c6a1feb54f065dc3c433ded18d0 Mon Sep 17 00:00:00 2001
From: Mrugesh Mohapatra 
Date: Sat, 19 May 2018 17:08:22 +0530
Subject: [PATCH 07/12] feat(auth0): add hosted pages to version control

---
 hosted-templates/hosted-pages/login.html | 456 +++++++++++++++++++++++
 1 file changed, 456 insertions(+)
 create mode 100644 hosted-templates/hosted-pages/login.html

diff --git a/hosted-templates/hosted-pages/login.html b/hosted-templates/hosted-pages/login.html
new file mode 100644
index 0000000000..0d53f5cf19
--- /dev/null
+++ b/hosted-templates/hosted-pages/login.html
@@ -0,0 +1,456 @@
+
+
+
+
+
+  
+  
+
+  Sign in to freeCodeCamp
+  
+  
+  
+  
+
+
+
+
+  
+
+  
+
+
+ +
+
+

Sign up (or sign in with your existing account)

+
+
+ +
+
+
+ + + +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + + + + +
+
+
+ + + + + +
+
+
+ + + + + + + + + + + From f9dd384ea27b32d7de83948e8ae20e43214aca03 Mon Sep 17 00:00:00 2001 From: Mrugesh Mohapatra Date: Sat, 19 May 2018 19:54:02 +0530 Subject: [PATCH 08/12] fix(auth0): Fix the scope to openid email --- hosted-templates/hosted-pages/login.html | 6 ++++-- server/passport-providers.js | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/hosted-templates/hosted-pages/login.html b/hosted-templates/hosted-pages/login.html index 0d53f5cf19..260840845e 100644 --- a/hosted-templates/hosted-pages/login.html +++ b/hosted-templates/hosted-pages/login.html @@ -380,6 +380,8 @@ domain: config.auth0Domain, clientID: config.clientID, redirectUri: config.callbackURL, + scope: 'openid email', + audience: 'https://' + config.auth0Domain + '/userinfo', responseType: 'code' }, config.internalOptions); @@ -441,11 +443,11 @@ } }); - $('#send-email-btn').click(function() { + $('#send-email-btn').click(function(){ sendEmail(); }); - $('#send-code-btn').click(function() { + $('#send-code-btn').click(function(){ login(); }); diff --git a/server/passport-providers.js b/server/passport-providers.js index 7ee12a9ceb..0f8a1ac505 100644 --- a/server/passport-providers.js +++ b/server/passport-providers.js @@ -178,7 +178,7 @@ export default { callbackPath: '/auth/auth0/callback', successRedirect: successRedirect, failureRedirect: failureRedirect, - scope: ['email'], + scope: ['openid email'], failureFlash: true } }; From 764d0405532ebe2646133b570c351f1963367d66 Mon Sep 17 00:00:00 2001 From: Mrugesh Mohapatra Date: Sun, 20 May 2018 00:50:55 +0530 Subject: [PATCH 09/12] fix(component-passport): Add falback redirects for external paths --- server/component-passport.js | 41 ++++++++++++++++++++++++++++++++++++ server/passport-providers.js | 3 ++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/server/component-passport.js b/server/component-passport.js index f08305a4f2..53497c92da 100644 --- a/server/component-passport.js +++ b/server/component-passport.js @@ -92,6 +92,47 @@ export default function setupPassport(app) { Object.keys(passportProviders).map(function(strategy) { var config = passportProviders[strategy]; config.session = config.session !== false; + + // https://stackoverflow.com/q/37430452 + let successRedirect = (req) => { + if (!!req && req.session && req.session.returnTo) { + var returnTo = req.session.returnTo; + delete req.session.returnTo; + return returnTo; + } + return config.successRedirect || ''; + }; + config.customCallback = !config.redirectWithToken + ? null + : function(req, res, next) { + var url = require('url'); + passport.authenticate( + strategy, + {session: false}, + function(err, user, info) { + if (err) { + return next(err); + } + + if (!user) { + return res.redirect(config.failureRedirect); + } + var redirect = url.parse(successRedirect(req), true); + + delete redirect.search; + + redirect.query = { + /* eslint-disable camelcase */ + access_token: info.accessToken.id, + /* eslint-enable camelcase */ + userId: user.id.toString() + }; + redirect = url.format(redirect); + return res.redirect(redirect); + } + )(req, res, next); + }; + configurator.configureProvider( strategy, { diff --git a/server/passport-providers.js b/server/passport-providers.js index 0f8a1ac505..4d408ec5b7 100644 --- a/server/passport-providers.js +++ b/server/passport-providers.js @@ -1,5 +1,5 @@ const successRedirect = '/settings'; -const failureRedirect = '/signin'; +const failureRedirect = '/'; const linkSuccessRedirect = '/settings'; const linkFailureRedirect = '/settings'; @@ -176,6 +176,7 @@ export default { callbackURL: '/auth/auth0/callback', authPath: '/auth/auth0', callbackPath: '/auth/auth0/callback', + redirectWithToken: false, successRedirect: successRedirect, failureRedirect: failureRedirect, scope: ['openid email'], From 4d48175e643f5c19102f3f6acea196ac940cc8cd Mon Sep 17 00:00:00 2001 From: Mrugesh Mohapatra Date: Sat, 19 May 2018 21:21:49 +0530 Subject: [PATCH 10/12] feat: update user identity login --- common/models/User-Identity.js | 166 +++++++++++--------- server/boot/authentication.js | 5 +- server/component-passport.js | 36 +++-- server/passport-providers.js | 4 +- server/views/account/deprecated-signup.jade | 9 +- 5 files changed, 124 insertions(+), 96 deletions(-) diff --git a/common/models/User-Identity.js b/common/models/User-Identity.js index 3fb7c30a40..0a716a75e7 100644 --- a/common/models/User-Identity.js +++ b/common/models/User-Identity.js @@ -4,8 +4,7 @@ import dedent from 'dedent'; import { getSocialProvider, - getUsernameFromProvider, - createUserUpdatesFromProfile + getUsernameFromProvider } from '../../server/utils/auth'; import { observeMethod, observeQuery } from '../../server/utils/rx'; import { wrapHandledError } from '../../server/utils/create-handled-error.js'; @@ -15,6 +14,7 @@ import { wrapHandledError } from '../../server/utils/create-handled-error.js'; export default function(UserIdent) { UserIdent.on('dataSourceAttached', () => { UserIdent.findOne$ = observeMethod(UserIdent, 'findOne'); + UserIdent.create$ = observeMethod(UserIdent, 'create'); }); // original source // github.com/strongloop/loopback-component-passport @@ -48,84 +48,98 @@ export default function(UserIdent) { }, include: 'user' }; - return UserIdent.findOne$(query) - .flatMap(identity => { - if (!identity) { - throw wrapHandledError( - new Error('user identity account not found'), - { - message: dedent` - New accounts can only be created using an email address. - Please create an account below - `, - type: 'info', - redirectTo: '/signup' - } - ); - } - const modified = new Date(); - const user = identity.user(); - if (!user) { - const username = getUsernameFromProvider(provider, profile); - return observeQuery( + + if (provider === 'auth0') { + + const email = profile.emails[0].value; + return User.findOne$({ where: { email } }) + .flatMap(user => { + if (!user) { + return User.create$({ email }); + } + return Observable.of(user); + }) + .subscribe( + ( user ) => cb(null, user, null, null), + cb + ); + + } else { + return UserIdent.findOne$(query) + .flatMap(identity => { + if (!identity) { + throw wrapHandledError( + new Error('user identity account not found'), + { + message: dedent` + New accounts can only be created using an email address. + Please create an account below + `, + type: 'info', + redirectTo: '/deprecated-signup' + } + ); + } + const modified = new Date(); + const user = identity.user(); + if (!user) { + const username = getUsernameFromProvider(provider, profile); + return observeQuery( + identity, + 'updateAttributes', + { + isOrphaned: username || true + } + ) + .do(() => { + throw wrapHandledError( + new Error('user identity is not associated with a user'), + { + type: 'info', + redirectTo: '/deprecated-signup', + message: dedent` + The user account associated with the ${provider} user ${username || 'Anon'} + no longer exists. + ` + } + ); + }); + } + + // identity already exists + // find user and log them in + identity.credentials = credentials; + const attributes = { + // we no longer want to keep the profile + // this is information we do not need or use + profile: null, + credentials: credentials, + modified + }; + const updateIdentity = observeQuery( identity, 'updateAttributes', + attributes + ); + const createToken = observeQuery( + AccessToken, + 'create', { - isOrphaned: username || true + userId: user.id, + created: new Date(), + ttl: user.constructor.settings.ttl } - ) - .do(() => { - throw wrapHandledError( - new Error('user identity is not associated with a user'), - { - type: 'info', - redirectTo: '/signup', - message: dedent` - The user account associated with the ${provider} user ${username || 'Anon'} - no longer exists. - ` - } - ); - }); - } - const updateUser = User.update$( - { id: user.id }, - createUserUpdatesFromProfile(provider, profile) - ).map(() => user); - // identity already exists - // find user and log them in - identity.credentials = credentials; - const attributes = { - // we no longer want to keep the profile - // this is information we do not need or use - profile: null, - credentials: credentials, - modified - }; - const updateIdentity = observeQuery( - identity, - 'updateAttributes', - attributes + ); + return Observable.combineLatest( + updateIdentity, + createToken, + (user, identity, token) => ({ user, identity, token }) + ); + }) + .subscribe( + ({ user, identity, token }) => cb(null, user, identity, token), + cb ); - const createToken = observeQuery( - AccessToken, - 'create', - { - userId: user.id, - created: new Date(), - ttl: user.constructor.settings.ttl - } - ); - return Observable.combineLatest( - updateUser, - updateIdentity, - createToken, - (user, identity, token) => ({ user, identity, token }) - ); - }) - .subscribe( - ({ user, identity, token }) => cb(null, user, identity, token), - cb - ); + } }; } diff --git a/server/boot/authentication.js b/server/boot/authentication.js index a3a9b05ad5..546bf6c755 100644 --- a/server/boot/authentication.js +++ b/server/boot/authentication.js @@ -30,9 +30,8 @@ module.exports = function enableAuthentication(app) { const { AuthToken, User } = app.models; router.get('/email-signin', (req, res) => res.redirect(301, '/login')); - router.get('/signin', (req, res) => res.redirect(301, '/login')); + router.get('/signin', (req, res) => res.redirect(301, '/signup')); router.get('/signout', (req, res) => res.redirect(301, '/logout')); - router.get('/signup', (req, res) => res.redirect(301, '/deprecated-signup')); function getLegacySignUp(req, res) { if (isSignUpDisabled) { @@ -44,7 +43,7 @@ module.exports = function enableAuthentication(app) { title: 'Sign in to freeCodeCamp using your Email Address' }); } - router.get('/deprecated-signup', ifUserRedirect, getLegacySignUp); + router.get('/signup', ifUserRedirect, getLegacySignUp); router.get('/login', ifUserRedirect, (req, res) => res.redirect(301, '/auth/auth0')); diff --git a/server/component-passport.js b/server/component-passport.js index 53497c92da..788dfe865e 100644 --- a/server/component-passport.js +++ b/server/component-passport.js @@ -2,6 +2,7 @@ import passport from 'passport'; import { PassportConfigurator } from '@freecodecamp/loopback-component-passport'; import passportProviders from './passport-providers'; +import url from 'url'; const passportOptions = { emailOptional: true, @@ -90,26 +91,27 @@ export default function setupPassport(app) { configurator.init(); Object.keys(passportProviders).map(function(strategy) { - var config = passportProviders[strategy]; + let config = passportProviders[strategy]; config.session = config.session !== false; // https://stackoverflow.com/q/37430452 let successRedirect = (req) => { if (!!req && req.session && req.session.returnTo) { - var returnTo = req.session.returnTo; + let returnTo = req.session.returnTo; delete req.session.returnTo; return returnTo; } return config.successRedirect || ''; }; + config.customCallback = !config.redirectWithToken ? null - : function(req, res, next) { - var url = require('url'); + : (req, res, next) => { + passport.authenticate( strategy, - {session: false}, - function(err, user, info) { + { session: false }, + (err, user) => { if (err) { return next(err); } @@ -117,16 +119,24 @@ export default function setupPassport(app) { if (!user) { return res.redirect(config.failureRedirect); } - var redirect = url.parse(successRedirect(req), true); + let redirect = url.parse(successRedirect(req), true); delete redirect.search; - redirect.query = { - /* eslint-disable camelcase */ - access_token: info.accessToken.id, - /* eslint-enable camelcase */ - userId: user.id.toString() - }; + req.flash( + 'success', + 'Success! You have signed in to your account. Happy Coding!' + ); + + // redirect.query = { + // /* eslint-disable camelcase */ + // access_token: info.accessToken.id, + // /* eslint-enable camelcase */ + // userId: user.id.toString() + // }; + + user.loginByRequest(req, res); + redirect = url.format(redirect); return res.redirect(redirect); } diff --git a/server/passport-providers.js b/server/passport-providers.js index 4d408ec5b7..b190b6d53c 100644 --- a/server/passport-providers.js +++ b/server/passport-providers.js @@ -1,4 +1,4 @@ -const successRedirect = '/settings'; +const successRedirect = '/'; const failureRedirect = '/'; const linkSuccessRedirect = '/settings'; const linkFailureRedirect = '/settings'; @@ -176,7 +176,7 @@ export default { callbackURL: '/auth/auth0/callback', authPath: '/auth/auth0', callbackPath: '/auth/auth0/callback', - redirectWithToken: false, + redirectWithToken: true, successRedirect: successRedirect, failureRedirect: failureRedirect, scope: ['openid email'], diff --git a/server/views/account/deprecated-signup.jade b/server/views/account/deprecated-signup.jade index 8d15ddffba..b46594a565 100644 --- a/server/views/account/deprecated-signup.jade +++ b/server/views/account/deprecated-signup.jade @@ -6,10 +6,15 @@ block content .text-center h2 Sign up (or sign in with your existing account) br + br + br a.btn.btn-lg.btn-primary(href='/auth/auth0') - | Get a link on your email + | Continue with your email .row .text-center + br + br + br br a(href="/deprecated-signin") - | Continute with old sign in methods + | Continute with an old sign in method, that you used previously. From c6b62fc7ab975db8718aeba3507dab2110f4431a Mon Sep 17 00:00:00 2001 From: Mrugesh Mohapatra Date: Sun, 20 May 2018 13:40:15 +0530 Subject: [PATCH 11/12] fix: add user object to Observables --- common/models/User-Identity.js | 7 ++++--- server/component-passport.js | 2 +- server/passport-providers.js | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/common/models/User-Identity.js b/common/models/User-Identity.js index 0a716a75e7..eeaa59f6a6 100644 --- a/common/models/User-Identity.js +++ b/common/models/User-Identity.js @@ -14,7 +14,6 @@ import { wrapHandledError } from '../../server/utils/create-handled-error.js'; export default function(UserIdent) { UserIdent.on('dataSourceAttached', () => { UserIdent.findOne$ = observeMethod(UserIdent, 'findOne'); - UserIdent.create$ = observeMethod(UserIdent, 'create'); }); // original source // github.com/strongloop/loopback-component-passport @@ -65,6 +64,7 @@ export default function(UserIdent) { ); } else { + return UserIdent.findOne$(query) .flatMap(identity => { if (!identity) { @@ -76,7 +76,7 @@ export default function(UserIdent) { Please create an account below `, type: 'info', - redirectTo: '/deprecated-signup' + redirectTo: '/signup' } ); } @@ -96,7 +96,7 @@ export default function(UserIdent) { new Error('user identity is not associated with a user'), { type: 'info', - redirectTo: '/deprecated-signup', + redirectTo: '/signup', message: dedent` The user account associated with the ${provider} user ${username || 'Anon'} no longer exists. @@ -131,6 +131,7 @@ export default function(UserIdent) { } ); return Observable.combineLatest( + Observable.of(user), updateIdentity, createToken, (user, identity, token) => ({ user, identity, token }) diff --git a/server/component-passport.js b/server/component-passport.js index 788dfe865e..63104737ff 100644 --- a/server/component-passport.js +++ b/server/component-passport.js @@ -104,7 +104,7 @@ export default function setupPassport(app) { return config.successRedirect || ''; }; - config.customCallback = !config.redirectWithToken + config.customCallback = !config.useCustomCallback ? null : (req, res, next) => { diff --git a/server/passport-providers.js b/server/passport-providers.js index b190b6d53c..bab270396b 100644 --- a/server/passport-providers.js +++ b/server/passport-providers.js @@ -176,7 +176,7 @@ export default { callbackURL: '/auth/auth0/callback', authPath: '/auth/auth0', callbackPath: '/auth/auth0/callback', - redirectWithToken: true, + useCustomCallback: true, successRedirect: successRedirect, failureRedirect: failureRedirect, scope: ['openid email'], From 6b49e75f2b80982ec8c16f717260082fe1f4d622 Mon Sep 17 00:00:00 2001 From: Mrugesh Mohapatra Date: Sun, 20 May 2018 14:28:55 +0530 Subject: [PATCH 12/12] fix(styles): Auth0 hosted page styles --- hosted-templates/hosted-pages/login.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/hosted-templates/hosted-pages/login.html b/hosted-templates/hosted-pages/login.html index 260840845e..4d1b4e6381 100644 --- a/hosted-templates/hosted-pages/login.html +++ b/hosted-templates/hosted-pages/login.html @@ -8,7 +8,8 @@ Sign in to freeCodeCamp - + +