diff --git a/packages/learn/gatsby-config.js b/packages/learn/gatsby-config.js index ccff2bb4d0..f0f751984b 100644 --- a/packages/learn/gatsby-config.js +++ b/packages/learn/gatsby-config.js @@ -9,7 +9,7 @@ module.exports = { siteUrl: 'https://learn.freecodecamp.org' }, proxy: { - prefix: '/', + prefix: '/api', url: 'http://localhost:3000' }, plugins: [ diff --git a/packages/learn/gatsby-node.js b/packages/learn/gatsby-node.js index a0ababf8cf..74ee5b7125 100644 --- a/packages/learn/gatsby-node.js +++ b/packages/learn/gatsby-node.js @@ -69,7 +69,13 @@ exports.createPages = ({ graphql, boundActionCreators }) => { // Create challenge pages. result.data.allChallengeNode.edges.forEach((edge, index, thisArray) => { - const { fields: { slug }, required = [], template, challengeType, id } = edge.node; + const { + fields: { slug }, + required = [], + template, + challengeType, + id + } = edge.node; const next = thisArray[index + 1]; const nextChallengePath = next ? next.node.fields.slug : '/'; createPage({ diff --git a/packages/learn/package.json b/packages/learn/package.json index 437c568898..1a05ea753d 100644 --- a/packages/learn/package.json +++ b/packages/learn/package.json @@ -5,6 +5,7 @@ "author": "Kyle Mathews ", "dependencies": { "adler32": "^0.1.7", + "auth0-js": "^9.4.2", "babel-core": "^6.26.0", "babel-jest": "^22.4.3", "babel-standalone": "^6.26.0", diff --git a/packages/learn/src/auth/index.js b/packages/learn/src/auth/index.js new file mode 100644 index 0000000000..a25b9b5a5d --- /dev/null +++ b/packages/learn/src/auth/index.js @@ -0,0 +1,120 @@ +import auth0 from 'auth0-js'; +import { navigateTo } from 'gatsby-link'; + +import { ajax$ } from '../templates/Challenges/utils/ajax-stream'; + +const AUTH0_DOMAIN = 'freecodecamp.auth0.com'; +const AUTH0_CLIENT_ID = 'vF70CJZyPKbZR4y0avecXXLkfyMNnyKn'; + +export default class Auth { + constructor() { + this.login = this.login.bind(this); + this.logout = this.logout.bind(this); + this.handleAuthentication = this.handleAuthentication.bind(this); + this.isAuthenticated = this.isAuthenticated.bind(this); + + this.sessionEmail = ''; + } + + auth0 = new auth0.WebAuth({ + domain: AUTH0_DOMAIN, + clientID: AUTH0_CLIENT_ID, + redirectUri: 'http://localhost:8000/callback', + audience: `https://${AUTH0_DOMAIN}/api/v2/`, + responseType: 'token id_token', + scope: 'openid profile email' + }); + + login() { + this.auth0.authorize(); + } + + logout() { + localStorage.removeItem('access_token'); + localStorage.removeItem('id_token'); + localStorage.removeItem('expires_at'); + localStorage.removeItem('user'); + } + + handleAuthentication() { + if (typeof window !== 'undefined') { + this.auth0.parseHash((err, authResult) => { + if (authResult && authResult.accessToken && authResult.idToken) { + this.setSession(authResult); + } else if (err) { + console.error(err); + } + + // Return to the homepage after authentication. + navigateTo('/'); + }); + } + } + + isAuthenticated() { + const expiresAt = JSON.parse(localStorage.getItem('expires_at')); + const isAuth = new Date().getTime() < expiresAt; + + // isAuth && this.getFCCUser(); + return isAuth; + } + + setSession(authResult) { + const expiresAt = JSON.stringify( + authResult.expiresIn * 9000 + new Date().getTime() + ); + localStorage.setItem('access_token', authResult.accessToken); + localStorage.setItem('id_token', authResult.idToken); + localStorage.setItem('expires_at', expiresAt); + + this.auth0.client.userInfo(authResult.accessToken, (err, user) => { + if (err) { + console.error(err); + } + localStorage.setItem('user', JSON.stringify(user)); + }); + } + + getFCCUser() { + const token = localStorage.getItem('access_token'); + if (!token) { + console.warn('no token found'); + } + const { email } = JSON.parse(localStorage.getItem('user')); + const headers = { + Authorization: `Bearer ${token}` + }; + const body = { email }; + ajax$({ + url: '/api/auth/auth0/login', + headers, + body + }).subscribe( + resp => { + console.info('YES'); + console.log(resp); + }, + err => { + console.warn('NO?'); + console.log(err.message); + }, + () => { + console.log('done'); + } + ); + } + + getUser() { + if (localStorage.getItem('user')) { + return JSON.parse(localStorage.getItem('user')); + } + return null; + } + + getUserName() { + if (this.getUser()) { + return this.getUser().name; + } + return null; + } +} diff --git a/packages/learn/src/components/Header/index.js b/packages/learn/src/components/Header/index.js index d0eb5617ec..7b1ae96415 100644 --- a/packages/learn/src/components/Header/index.js +++ b/packages/learn/src/components/Header/index.js @@ -1,32 +1,99 @@ -import React from 'react'; +import React, { Component } from 'react'; import Link from 'gatsby-link'; -const Header = () => ( -
-
-

- +
- Gatsby - -

-
-
-); +

+ + freeCodeCamp + +

+ {this.state.authenticated ? ( + + + Log Out + {auth.getUserName() && ({auth.getUserName()})} + + + ) : ( + + + Log In + + + )} + + + ); + } +} export default Header; diff --git a/packages/learn/src/pages/callback.js b/packages/learn/src/pages/callback.js new file mode 100644 index 0000000000..3cb72bbb26 --- /dev/null +++ b/packages/learn/src/pages/callback.js @@ -0,0 +1,12 @@ +import React from 'react'; +import Auth from '../auth'; + +function AuthCallBack() { + const auth = new Auth(); + auth.handleAuthentication(); + return

One moment please...

; +} + +AuthCallBack.displayName = 'AuthCallBack'; + +export default AuthCallBack; diff --git a/packages/learn/src/redux/app/index.js b/packages/learn/src/redux/app/index.js new file mode 100644 index 0000000000..c8afdfb28e --- /dev/null +++ b/packages/learn/src/redux/app/index.js @@ -0,0 +1,28 @@ +import { createAction, handleActions } from 'redux-actions'; + +import { createTypes } from '../../../utils/stateManagment'; + +const ns = 'app'; + +export const types = createTypes( + ['fetchUser', 'fetchUserComplete', 'fetchUserError'], + ns +); + +const initialState = { + user: {} +}; + +export const fetchUser = createAction(types.fetchUser); +export const fetchUserComplete = createAction(types.fetchUserComplete); +export const fecthUserError = createAction(types.fetchUserError); + +export const reducer = handleActions( + { + [types.fetchUserComplete]: (state, { payload }) => ({ + ...state, + user: payload + }) + }, + initialState +); diff --git a/packages/learn/src/redux/store.js b/packages/learn/src/redux/store.js index a6dcb6393b..5b6b2933d1 100644 --- a/packages/learn/src/redux/store.js +++ b/packages/learn/src/redux/store.js @@ -6,16 +6,19 @@ import { import { combineEpics, createEpicMiddleware } from 'redux-observable'; import { routerReducer as router, routerMiddleware } from 'react-router-redux'; +import { reducer as app } from './app'; import { reducer as challenge, epics } from '../templates/Challenges/redux'; import { reducer as map } from '../components/Map/redux'; const rootReducer = combineReducers({ + app, challenge, map, router }); const rootEpic = combineEpics(...epics); + const epicMiddleware = createEpicMiddleware(rootEpic, { dependencies: { window: typeof window !== 'undefined' ? window : {}, diff --git a/packages/learn/yarn.lock b/packages/learn/yarn.lock index 606135259b..d077bdca63 100644 --- a/packages/learn/yarn.lock +++ b/packages/learn/yarn.lock @@ -519,6 +519,17 @@ atob@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.0.tgz#ab2b150e51d7b122b9efc8d7340c06b6c41076bc" +auth0-js@^9.4.2: + version "9.4.2" + resolved "https://registry.yarnpkg.com/auth0-js/-/auth0-js-9.4.2.tgz#44363933266781fb9447ce09503c6f783b86a474" + dependencies: + base64-js "^1.2.0" + idtoken-verifier "^1.2.0" + qs "^6.4.0" + superagent "^3.8.2" + url-join "^1.1.0" + winchan "^0.2.0" + autoprefixer@^6.0.2, autoprefixer@^6.3.1: version "6.7.7" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014" @@ -1436,7 +1447,7 @@ base64-arraybuffer@0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" -base64-js@^1.0.2: +base64-js@^1.0.2, base64-js@^1.2.0: version "1.2.3" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.3.tgz#fb13668233d9614cf5fb4bce95a9ba4096cdf801" @@ -2211,7 +2222,7 @@ component-emitter@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.0.tgz#ccd113a86388d06482d03de3fc7df98526ba8efe" -component-emitter@1.2.1, component-emitter@^1.2.1: +component-emitter@1.2.1, component-emitter@^1.2.0, component-emitter@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" @@ -2307,6 +2318,10 @@ cookie@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" +cookiejar@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.1.tgz#41ad57b1b555951ec171412a81942b1e8200d34a" + copy-concurrently@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" @@ -2459,6 +2474,10 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-js@^3.1.9-1: + version "3.1.9-1" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.1.9-1.tgz#fda19e761fc077e01ffbfdc6e9fdfc59e8806cd8" + crypto-random-string@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" @@ -3822,6 +3841,14 @@ forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" +form-data@^2.3.1, form-data@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" + dependencies: + asynckit "^0.4.0" + combined-stream "1.0.6" + mime-types "^2.1.12" + form-data@~2.1.1: version "2.1.4" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" @@ -3830,13 +3857,9 @@ form-data@~2.1.1: combined-stream "^1.0.5" mime-types "^2.1.12" -form-data@~2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" - dependencies: - asynckit "^0.4.0" - combined-stream "1.0.6" - mime-types "^2.1.12" +formidable@^1.1.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.1.tgz#70fb7ca0290ee6ff961090415f4b3df3d2082659" forwarded@~0.1.2: version "0.1.2" @@ -4748,6 +4771,16 @@ icss-replace-symbols@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" +idtoken-verifier@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/idtoken-verifier/-/idtoken-verifier-1.2.0.tgz#4654f1f07ab7a803fc9b1b8b36057e2a87ad8b09" + dependencies: + base64-js "^1.2.0" + crypto-js "^3.1.9-1" + jsbn "^0.1.0" + superagent "^3.8.2" + url-join "^1.1.0" + ieee754@^1.1.4: version "1.1.11" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.11.tgz#c16384ffe00f5b7835824e67b6f2bd44a5229455" @@ -5626,7 +5659,7 @@ jsan@^3.1.5, jsan@^3.1.9: version "3.1.9" resolved "https://registry.yarnpkg.com/jsan/-/jsan-3.1.9.tgz#2705676c1058f0a7d9ac266ad036a5769cfa7c96" -jsbn@~0.1.0: +jsbn@^0.1.0, jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -6203,7 +6236,7 @@ merge@^1.1.3, merge@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" -methods@~1.1.2: +methods@^1.1.1, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -6283,7 +6316,7 @@ mime@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" -mime@^1.3.6, mime@^1.5.0: +mime@^1.3.6, mime@^1.4.1, mime@^1.5.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" @@ -7879,7 +7912,7 @@ q@^1.1.2: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" -qs@6.5.1, qs@~6.5.1: +qs@6.5.1, qs@^6.4.0, qs@^6.5.1, qs@~6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" @@ -9534,6 +9567,21 @@ style-loader@^0.13.0: dependencies: loader-utils "^1.0.2" +superagent@^3.8.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.2.tgz#e4a11b9d047f7d3efeb3bbe536d9ec0021d16403" + dependencies: + component-emitter "^1.2.0" + cookiejar "^2.1.0" + debug "^3.1.0" + extend "^3.0.0" + form-data "^2.3.1" + formidable "^1.1.1" + methods "^1.1.1" + mime "^1.4.1" + qs "^6.5.1" + readable-stream "^2.0.5" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -10026,6 +10074,10 @@ urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" +url-join@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/url-join/-/url-join-1.1.0.tgz#741c6c2f4596c4830d6718460920d0c92202dc78" + url-loader@^0.5.7: version "0.5.9" resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.5.9.tgz#cc8fea82c7b906e7777019250869e569e995c295" @@ -10389,6 +10441,10 @@ widest-line@^2.0.0: dependencies: string-width "^2.1.1" +winchan@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/winchan/-/winchan-0.2.0.tgz#3863028e7f974b0da1412f28417ba424972abd94" + window-size@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"