From daaa2b38cf15d9c15d1842860b9edca8982d0ea8 Mon Sep 17 00:00:00 2001 From: Tom <20648924+moT01@users.noreply.github.com> Date: Tue, 15 Mar 2022 03:41:43 -0500 Subject: [PATCH] feat: encode user tokens (#45429) --- api-server/src/server/boot/authentication.js | 2 +- api-server/src/server/boot/challenge.js | 15 +++++++++-- api-server/src/server/boot/user.js | 25 ++++++++++++++----- .../{delete-user-token.js => user-token.js} | 6 +++++ client/src/redux/codeally-saga.js | 4 +-- client/src/redux/user-token-saga.js | 5 +++- 6 files changed, 45 insertions(+), 12 deletions(-) rename api-server/src/server/middlewares/{delete-user-token.js => user-token.js} (77%) diff --git a/api-server/src/server/boot/authentication.js b/api-server/src/server/boot/authentication.js index e9df1cb6868..b55a90cdc49 100644 --- a/api-server/src/server/boot/authentication.js +++ b/api-server/src/server/boot/authentication.js @@ -16,7 +16,7 @@ import { wrapHandledError } from '../utils/create-handled-error.js'; import { removeCookies } from '../utils/getSetAccessToken'; import { ifUserRedirectTo, ifNoUserRedirectHome } from '../utils/middleware'; import { getRedirectParams } from '../utils/redirection'; -import { createDeleteUserToken } from '../middlewares/delete-user-token'; +import { createDeleteUserToken } from '../middlewares/user-token'; const passwordlessGetValidators = [ check('email') diff --git a/api-server/src/server/boot/challenge.js b/api-server/src/server/boot/challenge.js index f9f4eae6af7..e5c8c33d68a 100644 --- a/api-server/src/server/boot/challenge.js +++ b/api-server/src/server/boot/challenge.js @@ -12,6 +12,9 @@ import { Observable } from 'rx'; import isNumeric from 'validator/lib/isNumeric'; import isURL from 'validator/lib/isURL'; +import jwt from 'jsonwebtoken'; +import { jwtSecret } from '../../../../config/secrets'; + import { environment, deploymentEnv } from '../../../../config/env.json'; import { fixCompletedChallengeItem, @@ -399,14 +402,22 @@ function createCoderoadChallengeCompleted(app) { const { UserToken, User } = app.models; return async function coderoadChallengeCompleted(req, res) { - const { 'coderoad-user-token': userToken } = req.headers; + const { 'coderoad-user-token': encodedUserToken } = req.headers; const { tutorialId } = req.body; if (!tutorialId) return res.send(`'tutorialId' not found in request body`); - if (!userToken) + if (!encodedUserToken) return res.send(`'coderoad-user-token' not found in request headers`); + let userToken; + + try { + userToken = jwt.verify(encodedUserToken, jwtSecret)?.userToken; + } catch { + return res.send(`invalid user token`); + } + const tutorialRepo = tutorialId?.split(':')[0]; const tutorialOrg = tutorialRepo?.split('/')?.[0]; diff --git a/api-server/src/server/boot/user.js b/api-server/src/server/boot/user.js index 4c4baca1e9b..0bf4a4115f0 100644 --- a/api-server/src/server/boot/user.js +++ b/api-server/src/server/boot/user.js @@ -17,7 +17,10 @@ import { } from '../utils/publicUserProps'; import { getRedirectParams } from '../utils/redirection'; import { trimTags } from '../utils/validators'; -import { createDeleteUserToken } from '../middlewares/delete-user-token'; +import { + createDeleteUserToken, + encodeUserToken +} from '../middlewares/user-token'; const log = debugFactory('fcc:boot:user'); const sendNonUserToHome = ifNoUserRedirectHome(); @@ -64,16 +67,19 @@ function createPostUserToken(app) { return async function postUserToken(req, res) { const ttl = 900 * 24 * 60 * 60 * 1000; - let newToken; + let encodedUserToken; try { await UserToken.destroyAll({ userId: req.user.id }); - newToken = await UserToken.create({ ttl, userId: req.user.id }); + const newUserToken = await UserToken.create({ ttl, userId: req.user.id }); + + if (!newUserToken?.id) throw new Error(); + encodedUserToken = encodeUserToken(newUserToken.id); } catch (e) { return res.status(500).send('Error starting project'); } - return res.json({ token: newToken?.id }); + return res.json({ userToken: encodedUserToken }); }; } @@ -82,7 +88,7 @@ function deleteUserTokenResponse(req, res) { return res.status(500).send('Error deleting user token'); } - return res.send({ token: null }); + return res.send({ userToken: null }); } function createReadSessionUser(app) { @@ -94,7 +100,14 @@ function createReadSessionUser(app) { const userTokenArr = await queryUser.userTokens({ userId: queryUser.id }); + const userToken = userTokenArr[0]?.id; + let encodedUserToken; + + // only encode if a userToken was found + if (userToken) { + encodedUserToken = encodeUserToken(userToken); + } const source = queryUser && @@ -151,7 +164,7 @@ function createReadSessionUser(app) { isWebsite: !!user.website, ...normaliseUserFields(user), joinDate: user.id.getTimestamp(), - userToken + userToken: encodedUserToken } }, sessionMeta, diff --git a/api-server/src/server/middlewares/delete-user-token.js b/api-server/src/server/middlewares/user-token.js similarity index 77% rename from api-server/src/server/middlewares/delete-user-token.js rename to api-server/src/server/middlewares/user-token.js index 6101fa52a70..d841b9da7c5 100644 --- a/api-server/src/server/middlewares/delete-user-token.js +++ b/api-server/src/server/middlewares/user-token.js @@ -1,5 +1,7 @@ import debugFactory from 'debug'; const log = debugFactory('fcc:boot:user'); +import jwt from 'jsonwebtoken'; +import { jwtSecret } from '../../../../config/secrets'; /* * User tokens for submitting external curriculum are deleted when they sign @@ -23,3 +25,7 @@ export function createDeleteUserToken(app) { next(); }; } + +export function encodeUserToken(userToken) { + return jwt.sign({ userToken }, jwtSecret); +} diff --git a/client/src/redux/codeally-saga.js b/client/src/redux/codeally-saga.js index 90b7bbaa849..cc5c6a09221 100644 --- a/client/src/redux/codeally-saga.js +++ b/client/src/redux/codeally-saga.js @@ -24,8 +24,8 @@ function* tryToShowCodeAllySaga() { try { const response = yield call(postUserToken); - if (response?.token) { - yield put(updateUserToken(response.token)); + if (response?.userToken) { + yield put(updateUserToken(response.userToken)); yield put(showCodeAlly()); } else { yield put(createFlashMessage(startProjectErrMessage)); diff --git a/client/src/redux/user-token-saga.js b/client/src/redux/user-token-saga.js index 5c6d976939f..e99f9f0694b 100644 --- a/client/src/redux/user-token-saga.js +++ b/client/src/redux/user-token-saga.js @@ -19,7 +19,10 @@ function* deleteUserTokenSaga() { try { const response = yield call(deleteUserToken); - if (response && Object.prototype.hasOwnProperty.call(response, 'token')) { + if ( + response && + Object.prototype.hasOwnProperty.call(response, 'userToken') + ) { yield put(deleteUserTokenComplete()); yield put(createFlashMessage(message.deleted)); } else {