From 6fa9e03fa84110eb73dc520093522de94bb06ef4 Mon Sep 17 00:00:00 2001 From: jjwyse Date: Fri, 24 Mar 2017 20:39:21 -0600 Subject: [PATCH 1/2] :violin: giggity - validating authentication v1 --- src/server/api.js | 55 +++++++++++++++++++++++++++--------- src/server/db/user.js | 20 +++++++++++-- src/state/middleware/http.js | 2 ++ src/util/storage.js | 11 +++++++- 4 files changed, 71 insertions(+), 17 deletions(-) diff --git a/src/server/api.js b/src/server/api.js index e4b791c..fae3dc4 100644 --- a/src/server/api.js +++ b/src/server/api.js @@ -2,8 +2,9 @@ import fetch from 'isomorphic-fetch'; import bodyParser from 'body-parser'; import jwt from 'jsonwebtoken'; import fs from 'fs'; +import {isNil} from 'ramda'; -import {createSession, upsert} from './db/user'; +import {createToken, upsert, loadFromToken} from './db/user'; import logger from './logger'; import allPeaks from './db/data/all'; @@ -16,16 +17,14 @@ const CERT = fs.readFileSync('private.key'); * @param {object} res The express response object * @return {object} The newly created user and session token */ -const createUserAndSession = (stravaUser) => { - return upsert(stravaUser) - .then(sniktauUser => { - const jwtToken = jwt.sign({ data: sniktauUser }, CERT, { expiresIn: SEVEN_DAYS_IN_SECONDS }); - const session = {sniktauUserId: sniktauUser.id, bearerToken: stravaUser.access_token, token: jwtToken}; - return createSession(session) - .then(createdSession => { - return {id: createdSession.sniktau_user_id, token: createdSession.token, strava: {...stravaUser}}; - }); +const createUserAndSession = stravaUser => { + return upsert(stravaUser).then(sniktauUser => { + const jwtToken = jwt.sign({data: sniktauUser}, CERT, {expiresIn: SEVEN_DAYS_IN_SECONDS}); + const session = {sniktauUserId: sniktauUser.id, bearerToken: stravaUser.access_token, token: jwtToken}; + return createToken(session).then(createdSession => { + return {id: createdSession.sniktau_user_id, token: createdSession.token, strava: {...stravaUser}}; }); + }); }; /** @@ -58,8 +57,7 @@ const authenticate = (req, res) => { return null; } - return createUserAndSession(json) - .then(user => res.json(user)); + return createUserAndSession(json).then(user => res.json(user)); }); }) .catch(e => { @@ -79,7 +77,36 @@ const authenticate = (req, res) => { * @param {object} req The express request object * @param {object} res The express response object */ -const peaks = (req, res) => res.json(allPeaks); +const peaks = (req, res) => { + console.log(JSON.stringify(req.user)); + return res.json(allPeaks); +}; + +/** + * Ensures that the given request has a valid Bearer token + * @param {object} req The express request object + * @param {object} res The express response object + * @param {Function} next Next function + */ +const authMiddleware = (req, res, next) => { + // check if the user is authenticated and, if so, attach user to the request + const bearer = req.headers.authorization; + if (isNil(bearer)) { + res.status(401); + return res.json({error: 'Invalid bearer token'}); + } + + const token = bearer.split(' ')[1]; + return loadFromToken(token) + .then(user => { + if (!isNil(user)) { + req.user = user; + return next(); + } + res.status(401); + return res.json({error: 'Invalid bearer token'}); + }); +}; /** * Top level function that defines what functions will handle what API requests @@ -87,7 +114,7 @@ const peaks = (req, res) => res.json(allPeaks); */ const init = expressApp => { expressApp.use(bodyParser.json()); - expressApp.get('/api/peaks', peaks); + expressApp.get('/api/peaks', authMiddleware, peaks); expressApp.post('/api/oauth', authenticate); }; diff --git a/src/server/db/user.js b/src/server/db/user.js index 9056551..e707e4a 100644 --- a/src/server/db/user.js +++ b/src/server/db/user.js @@ -1,6 +1,7 @@ import {isNil} from 'ramda'; import pg from './pg'; +import logger from '../logger'; const toStravaDbUser = stravaUser => ({ id: stravaUser.athlete.id, @@ -74,7 +75,7 @@ const upsert = stravaUser => { * @param {string} bearerToken The strava OAuth bearer token * @return {string} The session token */ -const createSession = ({sniktauUserId, bearerToken, token})=> { +const createToken = ({sniktauUserId, bearerToken, token})=> { const newSession = { sniktau_user_id: sniktauUserId, token, strava_bearer_token: bearerToken }; return pg('user_session') .returning(['sniktau_user_id', 'token']) @@ -82,5 +83,20 @@ const createSession = ({sniktauUserId, bearerToken, token})=> { .then(sessions => sessions[0]); }; +/** + * Validates a user's session + * @param {string} token The bearer token + * @return {boolean} True if the bearer token is legit, false otherwise + */ +const loadFromToken = (token) => { + logger.log('Loading user session from token'); + return pg('user_session') + .innerJoin('sniktau_user', 'sniktau_user.id', 'user_session.sniktau_user_id') + .innerJoin('strava_user', 'strava_user.id', 'sniktau_user.strava_id') + .where({token: token}) + .select() + .then(users => isNil(users) ? null : users[0]); +}; + -export {create, createSession, update, upsert}; +export {create, createToken, update, upsert, loadFromToken}; diff --git a/src/state/middleware/http.js b/src/state/middleware/http.js index 874b981..88659d6 100644 --- a/src/state/middleware/http.js +++ b/src/state/middleware/http.js @@ -9,6 +9,7 @@ import fetch from 'isomorphic-fetch'; import {isNil, merge, pipe, reject} from 'ramda'; import {stringify} from 'querystring'; import {CALL_API, NOTIFICATIONS_ALERT, NOTIFICATIONS_MASK} from 'state/types'; +import {loadToken} from 'util/storage'; const createActionOfUnkownType = (actionType, options = {}) => { if (typeof actionType === 'object') { @@ -35,6 +36,7 @@ const httpMiddleware = store => next => action => { // any null values const createHeaders = pipe(merge(httpCall.headers), reject(isNil)); const headers = createHeaders({ + Authorization: `Bearer ${loadToken()}`, Accept: 'application/json', 'Content-Type': 'application/json', 'User-Agent': 'sniktau', diff --git a/src/util/storage.js b/src/util/storage.js index e78972e..8ce78b3 100644 --- a/src/util/storage.js +++ b/src/util/storage.js @@ -12,10 +12,19 @@ const save = json => localStorage.setItem(KEY, JSON.stringify(json)); */ const load = () => JSON.parse(localStorage.getItem(KEY)); +/** + * Loads the given authorization token from local storage + * @return {string} The authenticated user's token, or null + */ +const loadToken = () => { + const user = load() || {}; + return user.token; +}; + /** * * Invalidates the current user by removing authorization data from local storage */ const invalidate = () => localStorage.removeItem(KEY); -export {save, load, invalidate}; +export {save, load, loadToken, invalidate}; From 193c064363b1249f9a73bec25d6f40b864ab2db5 Mon Sep 17 00:00:00 2001 From: jjwyse Date: Fri, 24 Mar 2017 20:42:16 -0600 Subject: [PATCH 2/2] :book: debug --- src/server/api.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/server/api.js b/src/server/api.js index fae3dc4..517f234 100644 --- a/src/server/api.js +++ b/src/server/api.js @@ -77,10 +77,7 @@ const authenticate = (req, res) => { * @param {object} req The express request object * @param {object} res The express response object */ -const peaks = (req, res) => { - console.log(JSON.stringify(req.user)); - return res.json(allPeaks); -}; +const peaks = (req, res) => res.json(allPeaks); /** * Ensures that the given request has a valid Bearer token