fix(api): Use /internal for API entry

This commit is contained in:
Bouncey
2018-08-29 20:52:41 +01:00
committed by mrugesh mohapatra
parent 2be227e873
commit ef18f5a931
17 changed files with 133 additions and 142 deletions

View File

@ -17,13 +17,14 @@ import _ from 'lodash';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import generate from 'nanoid/generate'; import generate from 'nanoid/generate';
import { homeLocation } from '../../config/env';
import { fixCompletedChallengeItem } from '../utils'; import { fixCompletedChallengeItem } from '../utils';
import { themes } from '../utils/themes'; import { themes } from '../utils/themes';
import { saveUser, observeMethod } from '../../server/utils/rx.js'; import { saveUser, observeMethod } from '../../server/utils/rx.js';
import { blacklistedUsernames } from '../../server/utils/constants.js'; import { blacklistedUsernames } from '../../server/utils/constants.js';
import { wrapHandledError } from '../../server/utils/create-handled-error.js'; import { wrapHandledError } from '../../server/utils/create-handled-error.js';
import { import {
getServerFullURL,
getEmailSender getEmailSender
} from '../../server/utils/url-utils.js'; } from '../../server/utils/url-utils.js';
import { import {
@ -255,7 +256,7 @@ module.exports = function(User) {
throw wrapHandledError( throw wrapHandledError(
new Error('user already exists'), new Error('user already exists'),
{ {
redirectTo: '/signin', redirectTo: `${homeLocation}/signin`,
message: dedent` message: dedent`
The ${user.email} email address is already associated with an account. The ${user.email} email address is already associated with an account.
Try signing in with it here instead. Try signing in with it here instead.
@ -595,7 +596,7 @@ module.exports = function(User) {
} }
const { id: loginToken, created: emailAuthLinkTTL } = token; const { id: loginToken, created: emailAuthLinkTTL } = token;
const loginEmail = this.getEncodedEmail(newEmail ? newEmail : null); const loginEmail = this.getEncodedEmail(newEmail ? newEmail : null);
const host = getServerFullURL(); const host = homeLocation;
const mailOptions = { const mailOptions = {
type: 'email', type: 'email',
to: newEmail ? newEmail : this.email, to: newEmail ? newEmail : this.email,

3
config/env.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
homeLocation: process.env.HOME_LOCATION
};

View File

@ -3,7 +3,7 @@ module.exports = {
title: 'Gatsby Default Starter' title: 'Gatsby Default Starter'
}, },
proxy: { proxy: {
prefix: '/external', prefix: '/internal',
url: 'http://localhost:3000' url: 'http://localhost:3000'
}, },
plugins: [ plugins: [

View File

@ -18,3 +18,5 @@ PEER=stuff
DEBUG=true DEBUG=true
IMAGE_BASE_URL='https://s3.amazonaws.com/freecodecamp/images/' IMAGE_BASE_URL='https://s3.amazonaws.com/freecodecamp/images/'
HOME_LOCATION='http://localhost:8000'

View File

@ -16,4 +16,5 @@ export default function bootServices(app) {
const middleware = Fetchr.middleware(); const middleware = Fetchr.middleware();
app.use('/services', middleware); app.use('/services', middleware);
app.use('/external/services', middleware); app.use('/external/services', middleware);
app.use('/internal/services', middleware);
} }

View File

@ -5,13 +5,14 @@ import dedent from 'dedent';
import { isEmail } from 'validator'; import { isEmail } from 'validator';
import { check } from 'express-validator/check'; import { check } from 'express-validator/check';
import { homeLocation } from '../../config/env';
import { import {
ifUserRedirectTo, ifUserRedirectTo,
ifNoUserRedirectTo, ifNoUserRedirectTo,
createValidatorErrorHandler createValidatorErrorHandler
} from '../utils/middleware'; } from '../utils/middleware';
import { wrapHandledError } from '../utils/create-handled-error.js'; import { wrapHandledError } from '../utils/create-handled-error.js';
import { homeURL } from '../../common/utils/constantStrings.json';
const isSignUpDisabled = !!process.env.DISABLE_SIGNUP; const isSignUpDisabled = !!process.env.DISABLE_SIGNUP;
// const debug = debugFactory('fcc:boot:auth'); // const debug = debugFactory('fcc:boot:auth');
@ -24,43 +25,21 @@ module.exports = function enableAuthentication(app) {
// loopback.io/doc/en/lb2/Authentication-authorization-and-permissions.html // loopback.io/doc/en/lb2/Authentication-authorization-and-permissions.html
app.enableAuth(); app.enableAuth();
const ifUserRedirect = ifUserRedirectTo(); const ifUserRedirect = ifUserRedirectTo();
const ifNoUserRedirectHome = ifNoUserRedirectTo(homeURL); const ifNoUserRedirectHome = ifNoUserRedirectTo(homeLocation);
const router = app.loopback.Router();
const api = app.loopback.Router(); const api = app.loopback.Router();
const { AuthToken, User } = app.models; const { AuthToken, User } = app.models;
router.get('/signup', (req, res) => res.redirect(301, '/signin')); api.get('/signin', ifUserRedirect, (req, res) => res.redirect('/auth/auth0'));
router.get('/email-signin', (req, res) => res.redirect(301, '/signin'));
router.get('/login', (req, res) => res.redirect(301, '/signin'));
router.get('/deprecated-signin', (req, res) => res.redirect(301, '/signin'));
router.get('/logout', (req, res) => res.redirect(301, '/signout')); api.get('/signout', (req, res) => {
router.get('/signin',
ifUserRedirect,
(req, res) => res.redirect('/auth/auth0')
);
router.get(
'/update-email',
ifNoUserRedirectHome,
(req, res) => res.render('account/update-email', {
title: 'Update your email'
})
);
router.get('/signout', (req, res) => {
req.logout(); req.logout();
req.session.destroy( (err) => { req.session.destroy(err => {
if (err) { if (err) {
throw wrapHandledError( throw wrapHandledError(new Error('could not destroy session'), {
new Error('could not destroy session'), type: 'info',
{ message: 'Oops, something is not right.',
type: 'info', redirectTo: homeLocation
message: 'Oops, something is not right.', });
redirectTo: '/'
}
);
} }
const config = { const config = {
signed: !!req.signedCookies, signed: !!req.signedCookies,
@ -70,8 +49,8 @@ module.exports = function enableAuthentication(app) {
res.clearCookie('access_token', config); res.clearCookie('access_token', config);
res.clearCookie('userId', config); res.clearCookie('userId', config);
res.clearCookie('_csrf', config); res.clearCookie('_csrf', config);
res.redirect('/'); res.redirect(homeLocation);
}); });
}); });
const defaultErrorMsg = dedent` const defaultErrorMsg = dedent`
@ -93,112 +72,105 @@ module.exports = function enableAuthentication(app) {
function getPasswordlessAuth(req, res, next) { function getPasswordlessAuth(req, res, next) {
const { const {
query: { query: { email: encodedEmail, token: authTokenId, emailChange } = {}
email: encodedEmail,
token: authTokenId,
emailChange
} = {}
} = req; } = req;
const email = User.decodeEmail(encodedEmail); const email = User.decodeEmail(encodedEmail);
if (!isEmail(email)) { if (!isEmail(email)) {
return next(wrapHandledError( return next(
new TypeError('decoded email is invalid'), wrapHandledError(new TypeError('decoded email is invalid'), {
{
type: 'info', type: 'info',
message: 'The email encoded in the link is incorrectly formatted', message: 'The email encoded in the link is incorrectly formatted',
redirectTo: '/signin' redirectTo: `${homeLocation}/signin`
} })
)); );
} }
// first find // first find
return AuthToken.findOne$({ where: { id: authTokenId } }) return (
.flatMap(authToken => { AuthToken.findOne$({ where: { id: authTokenId } })
if (!authToken) { .flatMap(authToken => {
throw wrapHandledError( if (!authToken) {
new Error(`no token found for id: ${authTokenId}`), throw wrapHandledError(
{ new Error(`no token found for id: ${authTokenId}`),
type: 'info', {
message: defaultErrorMsg, type: 'info',
redirectTo: '/signin' message: defaultErrorMsg,
} redirectTo: `${homeLocation}/signin`
); }
} );
// find user then validate and destroy email validation token }
// finally retun user instance // find user then validate and destroy email validation token
return User.findOne$({ where: { id: authToken.userId } }) // finally retun user instance
.flatMap(user => { return User.findOne$({ where: { id: authToken.userId } }).flatMap(
if (!user) { user => {
throw wrapHandledError( if (!user) {
new Error(`no user found for token: ${authTokenId}`),
{
type: 'info',
message: defaultErrorMsg,
redirectTo: '/signin'
}
);
}
if (user.email !== email) {
if (!emailChange || (emailChange && user.newEmail !== email)) {
throw wrapHandledError( throw wrapHandledError(
new Error('user email does not match'), new Error(`no user found for token: ${authTokenId}`),
{ {
type: 'info', type: 'info',
message: defaultErrorMsg, message: defaultErrorMsg,
redirectTo: '/signin' redirectTo: `${homeLocation}/signin`
} }
); );
} }
} if (user.email !== email) {
return authToken.validate$() if (!emailChange || (emailChange && user.newEmail !== email)) {
.map(isValid => {
if (!isValid) {
throw wrapHandledError( throw wrapHandledError(
new Error('token is invalid'), new Error('user email does not match'),
{ {
type: 'info',
message: defaultErrorMsg,
redirectTo: `${homeLocation}/signin`
}
);
}
}
return authToken
.validate$()
.map(isValid => {
if (!isValid) {
throw wrapHandledError(new Error('token is invalid'), {
type: 'info', type: 'info',
message: ` message: `
Looks like the link you clicked has expired, Looks like the link you clicked has expired,
please request a fresh link, to sign in. please request a fresh link, to sign in.
`, `,
redirectTo: '/signin' redirectTo: `${homeLocation}/signin`
} });
); }
} return authToken.destroy$();
return authToken.destroy$(); })
}) .map(() => user);
.map(() => user); }
}); );
}) })
// at this point token has been validated and destroyed // at this point token has been validated and destroyed
// update user and log them in // update user and log them in
.map(user => user.loginByRequest(req, res)) .map(user => user.loginByRequest(req, res))
.do(() => { .do(() => {
req.flash( req.flash(
'success', 'success',
'Success! You have signed in to your account. Happy Coding!' 'Success! You have signed in to your account. Happy Coding!'
); );
return res.redirect('/'); return res.redirect(homeLocation);
}) })
.subscribe( .subscribe(() => {}, next)
() => {}, );
next
);
} }
router.get( api.get(
'/passwordless-auth', '/passwordless-auth',
ifUserRedirect, ifUserRedirect,
passwordlessGetValidators, passwordlessGetValidators,
createValidatorErrorHandler('errors', '/signin'), createValidatorErrorHandler('errors', `${homeLocation}/signin`),
getPasswordlessAuth getPasswordlessAuth
); );
router.get( api.get('/passwordless-change', (req, res) =>
'/passwordless-change', res.redirect(301, '/confirm-email')
(req, res) => res.redirect(301, '/confirm-email')
); );
router.get(
api.get(
'/confirm-email', '/confirm-email',
ifNoUserRedirectHome, ifNoUserRedirectHome,
passwordlessGetValidators, passwordlessGetValidators,
@ -214,21 +186,18 @@ module.exports = function enableAuthentication(app) {
const { body: { email } = {} } = req; const { body: { email } = {} } = req;
return User.findOne$({ where: { email } }) return User.findOne$({ where: { email } })
.flatMap(_user => Observable.if( .flatMap(_user =>
Observable.if(
// if no user found create new user and save to db // if no user found create new user and save to db
_.constant(_user), _.constant(_user),
Observable.of(_user), Observable.of(_user),
User.create$({ email }) User.create$({ email })
) ).flatMap(user => user.requestAuthEmail(!_user))
.flatMap(user => user.requestAuthEmail(!_user))
) )
.do(msg => { .do(msg => {
let redirectTo = '/'; let redirectTo = homeLocation;
if ( if (req.session && req.session.returnTo) {
req.session &&
req.session.returnTo
) {
redirectTo = req.session.returnTo; redirectTo = req.session.returnTo;
} }
@ -242,10 +211,10 @@ module.exports = function enableAuthentication(app) {
'/passwordless-auth', '/passwordless-auth',
ifUserRedirect, ifUserRedirect,
passwordlessPostValidators, passwordlessPostValidators,
createValidatorErrorHandler('errors', '/signin'), createValidatorErrorHandler('errors', `${homeLocation}/signin`),
postPasswordlessAuth postPasswordlessAuth
); );
app.use(router);
app.use(api); app.use(api);
app.use('/internal', api);
}; };

View File

@ -169,6 +169,7 @@ export default function(app) {
app.use(api); app.use(api);
app.use('/external', api); app.use('/external', api);
app.use('/internal', api);
app.use(router); app.use(router);
function modernChallengeCompleted(req, res, next) { function modernChallengeCompleted(req, res, next) {

View File

@ -3,6 +3,8 @@ import { Observable } from 'rx';
import debugFactory from 'debug'; import debugFactory from 'debug';
import dedent from 'dedent'; import dedent from 'dedent';
import { homeLocation } from '../../config/env';
import nonprofits from '../utils/commit.json'; import nonprofits from '../utils/commit.json';
import { import {
commitGoals, commitGoals,
@ -23,7 +25,7 @@ import {
} from '../utils/middleware'; } from '../utils/middleware';
const sendNonUserToSignIn = ifNoUserRedirectTo( const sendNonUserToSignIn = ifNoUserRedirectTo(
'/signin', `${homeLocation}/signin`,
'You must be signed in to commit to a nonprofit.', 'You must be signed in to commit to a nonprofit.',
'info' 'info'
); );

View File

@ -175,7 +175,7 @@ export default function settingsController(app) {
refetchCompletedChallenges refetchCompletedChallenges
); );
api.post('/update-flags', ifNoUser401, updateFlags); api.post('/update-flags', ifNoUser401, updateFlags);
api.post( api.put(
'/update-my-email', '/update-my-email',
ifNoUser401, ifNoUser401,
updateMyEmailValidators, updateMyEmailValidators,
@ -190,7 +190,7 @@ export default function settingsController(app) {
updateMyCurrentChallenge updateMyCurrentChallenge
); );
api.post( api.post(
'/external/update-my-current-challenge', '/update-my-current-challenge',
ifNoUser401, ifNoUser401,
updateMyCurrentChallengeValidators, updateMyCurrentChallengeValidators,
createValidatorErrorHandler(alertTypes.danger), createValidatorErrorHandler(alertTypes.danger),
@ -209,5 +209,6 @@ export default function settingsController(app) {
api.post('/update-my-username', ifNoUser401, updateMyUsername); api.post('/update-my-username', ifNoUser401, updateMyUsername);
app.use('/external', api); app.use('/external', api);
app.use('/internal', api);
app.use(api); app.use(api);
} }

View File

@ -42,9 +42,9 @@ module.exports = function bootUser(app) {
getReportUserProfile getReportUserProfile
); );
app.use(router); app.use(router);
app.use('/external', api); app.use('/external', api);
app.use('/internal', api);
}; };
function readSessionUser(req, res, next) { function readSessionUser(req, res, next) {

View File

@ -11,7 +11,7 @@ export default function() {
return function csrf(req, res, next) { return function csrf(req, res, next) {
const path = req.path.split('/')[1]; const path = req.path.split('/')[1];
if (/(api|external|^p$)/.test(path)) { if (/(^api$|^external$|^internal$|^p$)/.test(path)) {
return next(); return next();
} }
return protection(req, res, next); return protection(req, res, next);

View File

@ -1,6 +1,9 @@
import { inspect } from 'util'; import { inspect } from 'util';
import _ from 'lodash/fp'; import _ from 'lodash/fp';
import accepts from 'accepts'; import accepts from 'accepts';
import { homeLocation } from '../../config/env';
import { unwrapHandledError } from '../utils/create-handled-error.js'; import { unwrapHandledError } from '../utils/create-handled-error.js';
const isDev = process.env.NODE_ENV !== 'production'; const isDev = process.env.NODE_ENV !== 'production';
@ -57,7 +60,7 @@ export default function prodErrorHandler() {
const accept = accepts(req); const accept = accepts(req);
const type = accept.type('html', 'json', 'text'); const type = accept.type('html', 'json', 'text');
const redirectTo = handled.redirectTo || '/'; const redirectTo = handled.redirectTo || `${homeLocation}/`;
const message = handled.message || const message = handled.message ||
'Oops! Something went wrong. Please try again later'; 'Oops! Something went wrong. Please try again later';

View File

@ -2,11 +2,13 @@ import loopback from 'loopback';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import { isBefore } from 'date-fns'; import { isBefore } from 'date-fns';
import { homeLocation } from '../../config/env';
import { wrapHandledError } from '../utils/create-handled-error'; import { wrapHandledError } from '../utils/create-handled-error';
export default () => function authorizeByJWT(req, res, next) { export default () => function authorizeByJWT(req, res, next) {
const path = req.path.split('/')[1]; const path = req.path.split('/')[1];
if (/external/.test(path)) { if (/^external$|^internal$/.test(path)) {
const cookie = req.signedCookies && req.signedCookies['jwt_access_token'] || const cookie = req.signedCookies && req.signedCookies['jwt_access_token'] ||
req.cookie && req.cookie['jwt_access_token']; req.cookie && req.cookie['jwt_access_token'];
if (!cookie) { if (!cookie) {
@ -14,7 +16,7 @@ export default () => function authorizeByJWT(req, res, next) {
new Error('Access token is required for this request'), new Error('Access token is required for this request'),
{ {
type: 'info', type: 'info',
redirect: '/signin', redirect: `${homeLocation}/signin`,
message: 'Access token is required for this request', message: 'Access token is required for this request',
status: 403 status: 403
} }
@ -28,7 +30,7 @@ export default () => function authorizeByJWT(req, res, next) {
new Error(err.message), new Error(err.message),
{ {
type: 'info', type: 'info',
redirct: '/signin', redirect: `${homeLocation}/signin`,
message: 'Your access token is invalid', message: 'Your access token is invalid',
status: 403 status: 403
} }
@ -41,7 +43,7 @@ export default () => function authorizeByJWT(req, res, next) {
new Error('Access token is no longer vaild'), new Error('Access token is no longer vaild'),
{ {
type: 'info', type: 'info',
redirect: '/signin', redirect: `${homeLocation}/signin`,
message: 'Access token is no longer vaild', message: 'Access token is no longer vaild',
status: 403 status: 403
} }

View File

@ -1,6 +1,6 @@
Here's your sign in link. It will instantly sign you into freeCodeCamp.org - no password necessary: Here's your sign in link. It will instantly sign you into freeCodeCamp.org - no password necessary:
<%= host %>/passwordless-auth/?email=<%= loginEmail %>&token=<%= loginToken %> <%= host %>/internal/passwordless-auth/?email=<%= loginEmail %>&token=<%= loginToken %>
Note: this sign in link will expire after 15 minutes. If you need a new sign in link, go to https://www.freecodecamp.org/signin Note: this sign in link will expire after 15 minutes. If you need a new sign in link, go to https://www.freecodecamp.org/signin

View File

@ -1,10 +1,10 @@
Welcome to the freeCodeCamp community! Welcome to the freeCodeCamp community!
We have created a new account for you. We have created a new account for you.
Here's your sign in link. It will instantly sign you into freeCodeCamp.org - no password necessary: Here's your sign in link. It will instantly sign you into freeCodeCamp.org - no password necessary:
<%= host %>/passwordless-auth/?email=<%= loginEmail %>&token=<%= loginToken %> <%= host %>/internal/passwordless-auth/?email=<%= loginEmail %>&token=<%= loginToken %>
Note: this sign in link will expire after 15 minutes. If you need a new sign in link, go to https://www.freecodecamp.org/signin Note: this sign in link will expire after 15 minutes. If you need a new sign in link, go to https://www.freecodecamp.org/signin

View File

@ -1,6 +1,6 @@
Please confirm this address for freeCodeCamp: Please confirm this address for freeCodeCamp:
<%= host %>/confirm-email?email=<%= loginEmail %>&token=<%= loginToken %>&emailChange=<%= emailChange %> <%= host %>/internal/confirm-email?email=<%= loginEmail %>&token=<%= loginToken %>&emailChange=<%= emailChange %>
Happy coding! Happy coding!

View File

@ -1,15 +1,17 @@
import axios from 'axios'; import axios from 'axios';
const base = '/internal';
function get(path) { function get(path) {
return axios.get(`/external${path}`); return axios.get(`${base}${path}`);
} }
function post(path, body) { function post(path, body) {
return axios.post(`/external${path}`, body); return axios.post(`${base}${path}`, body);
} }
function put(path, body) { function put(path, body) {
return axios.put(`/external${path}`, body); return axios.put(`${base}${path}`, body);
} }
function sniff(things) { function sniff(things) {
@ -22,5 +24,9 @@ export function getSessionUser() {
} }
export function putUserAcceptsTerms(quincyEmails) { export function putUserAcceptsTerms(quincyEmails) {
return put('/update-privacy-terms', {quincyEmails}) return put('/update-privacy-terms', { quincyEmails });
}
export function putUserUpdateEmail(email) {
return put('/update-my-email', { email });
} }