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 generate from 'nanoid/generate';
import { homeLocation } from '../../config/env';
import { fixCompletedChallengeItem } from '../utils';
import { themes } from '../utils/themes';
import { saveUser, observeMethod } from '../../server/utils/rx.js';
import { blacklistedUsernames } from '../../server/utils/constants.js';
import { wrapHandledError } from '../../server/utils/create-handled-error.js';
import {
getServerFullURL,
getEmailSender
} from '../../server/utils/url-utils.js';
import {
@ -255,7 +256,7 @@ module.exports = function(User) {
throw wrapHandledError(
new Error('user already exists'),
{
redirectTo: '/signin',
redirectTo: `${homeLocation}/signin`,
message: dedent`
The ${user.email} email address is already associated with an account.
Try signing in with it here instead.
@ -595,7 +596,7 @@ module.exports = function(User) {
}
const { id: loginToken, created: emailAuthLinkTTL } = token;
const loginEmail = this.getEncodedEmail(newEmail ? newEmail : null);
const host = getServerFullURL();
const host = homeLocation;
const mailOptions = {
type: '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'
},
proxy: {
prefix: '/external',
prefix: '/internal',
url: 'http://localhost:3000'
},
plugins: [

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,10 @@
Welcome to the freeCodeCamp community!
Welcome to the freeCodeCamp community!
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:
<%= 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

View File

@ -1,6 +1,6 @@
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!

View File

@ -1,15 +1,17 @@
import axios from 'axios';
const base = '/internal';
function get(path) {
return axios.get(`/external${path}`);
return axios.get(`${base}${path}`);
}
function post(path, body) {
return axios.post(`/external${path}`, body);
return axios.post(`${base}${path}`, body);
}
function put(path, body) {
return axios.put(`/external${path}`, body);
return axios.put(`${base}${path}`, body);
}
function sniff(things) {
@ -22,5 +24,9 @@ export function getSessionUser() {
}
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 });
}