Skip to content

Commit

Permalink
Merge pull request #10 from jjwyse/auth-validation
Browse files Browse the repository at this point in the history
🎻 Validating bearer token on API requests
  • Loading branch information
jjwyse committed Mar 25, 2017
2 parents 3dcf6ff + 193c064 commit 2959d74
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 16 deletions.
50 changes: 37 additions & 13 deletions src/server/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand 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}};
});
});
};

/**
Expand Down Expand Up @@ -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 => {
Expand All @@ -81,13 +79,39 @@ const authenticate = (req, res) => {
*/
const peaks = (req, res) => 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
* @param {object} expressApp The express app to add any API definitions to
*/
const init = expressApp => {
expressApp.use(bodyParser.json());
expressApp.get('/api/peaks', peaks);
expressApp.get('/api/peaks', authMiddleware, peaks);
expressApp.post('/api/oauth', authenticate);
};

Expand Down
20 changes: 18 additions & 2 deletions src/server/db/user.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {isNil} from 'ramda';

import pg from './pg';
import logger from '../logger';

const toStravaDbUser = stravaUser => ({
id: stravaUser.athlete.id,
Expand Down Expand Up @@ -74,13 +75,28 @@ 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'])
.insert(newSession)
.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};
2 changes: 2 additions & 0 deletions src/state/middleware/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand All @@ -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',
Expand Down
11 changes: 10 additions & 1 deletion src/util/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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};

0 comments on commit 2959d74

Please sign in to comment.