Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .coveralls.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
service_name: travis-ci
repo_token: DKy5uFkFBqXlNCdxn1hgRZ7WNJzzwQl11
25 changes: 25 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
install:
- npm install
script:
- npm run test

language: node_js
node_js:
- 'stable'
services:
- mongodb
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- gcc-4.8
- g++-4.8
env:
- CXX=g++-4.8
before_install:
- cd lab-nathan
sudo: required
before_script: npm i
script:
- npm run test
6 changes: 6 additions & 0 deletions lab-nathan/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
**/node_modules/*
**/vendor/*
**/*.min.js
**/coverage/*
**/build/*
**/assets/*
22 changes: 22 additions & 0 deletions lab-nathan/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"rules": {
"no-console": "off",
"indent": [ "error", 2 ],
"quotes": [ "error", "single" ],
"semi": ["error", "always"],
},
"env": {
"es6": true,
"node": true,
"mocha": true,
"jasmine": true
},
"parserOptions": {
"ecmaFeatures": {
"modules": true,
"experimentalObjectRestSpread": true,
"impliedStrict": true
}
},
"extends": "eslint:recommended"
}
3 changes: 3 additions & 0 deletions lab-nathan/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
.env
coverage
3 changes: 3 additions & 0 deletions lab-nathan/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# CF-Rolodex

This project is a REST API which allows token-authenticated users to manage contacts and their corresponding pictures. Picture resources are stored on AWS S3.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 66 additions & 0 deletions lab-nathan/middleware/basic-authentication.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
'use strict';

const createError = require('http-errors');
const debug = require('debug')('authentication-server:basic-authentication');

let basicAuthentication = function(request, response, next) {
debug('basicAuthentication');

let authorization = request.headers.authorization;

if (!authorization) {
let error = createError(401, 'Authorization header not provided.');
return next(error);
}

let base64CredentialArray = authorization.split('Basic ');

if (base64CredentialArray.length < 2) {
let error = createError(401, 'Invalid authorization header format.');
return next(error);
}

let base64Credentials = base64CredentialArray[1];

if (!base64Credentials) {
let error = createError(401, 'Authorization header credentials not provided.');
return next(error);
}

let base64CredentialBuffer = Buffer.from(base64Credentials, 'base64');
let credentials = base64CredentialBuffer.toString();

if (!credentials) {
let error = createError(401, 'Authorization header credentials not provided.');
return next(error);
}

let credentialArray = credentials.split(':');

if (credentialArray.length < 2) {
let error = createError(401, 'Invalid credential format.');
return next(error);
}

let username = credentialArray[0];
let password = credentialArray[1];

if (!username) {
let error = createError(401, 'Invalid username.');
return next(error);
}

if (!password) {
let error = createError(401, 'Password not provided.');
return next(error);
}

request.authorization = {
username: username,
password: password
};

next();
};

module.exports = basicAuthentication;
50 changes: 50 additions & 0 deletions lab-nathan/middleware/bearer-authentication.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use strict';

const createError = require('http-errors');
const debug = require('debug')('cf-rolodex:bearer-authentication');
const jwt = require('jsonwebtoken');
const User = require('../model/user.js');

const bearerAuthentication = function(request, response, next) {
debug('bearerAuthentication');

let authorization = request.headers.authorization;

if (!authorization) {
let error = createError(401, 'Authorization header not provided.');
return next(error);
}

let authenticationArray = authorization.split('Bearer ');

if (authenticationArray.length < 2) {
let error = createError(401, 'Invalid authorization header format.');
return next(error);
}

let token = authenticationArray[1];

if (!token) {
let error = createError(401, 'Authentication token not provided.');
return next(error);
}

jwt.verify(token, process.env.APP_SECRET, (error, decoded) => {
if (error) {
let error = createError(401, 'Unauthorized token.');
return next(error);
}

User.findOne({ findHash: decoded.token })
.then(user => {
request.user = user;
next();
})
.catch(error => {
error = createError(401, error.message);
next(error);
});
});
};

module.exports = bearerAuthentication;
26 changes: 26 additions & 0 deletions lab-nathan/middleware/errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict';

const createError = require('http-errors');
const debug = require('debug')('cf-rolodex:errors');

const errors = function(error, request, response, next) {
if (!next) {
error = createError(404, error.message);
}

if (error.name === 'ValidationError') {
error = createError(400, error.message);
}

if (!error.status) {
error = createError(500, error.message);
}

debug('error name: ' + error.name);
debug('error message: ' + error.message);

response.status(error.status).send(error.name);
next();
};

module.exports = errors;
13 changes: 13 additions & 0 deletions lab-nathan/model/contact.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use strict';

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const contactSchema = Schema({
name: { type: String, required: true },
email: { type: String, required: true },
phone: { type: String, required: true },
userId: { type: Schema.Types.ObjectId, required: true }
});

module.exports = mongoose.model('contact', contactSchema);
17 changes: 17 additions & 0 deletions lab-nathan/model/picture.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict';

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const pictureSchema = Schema({
name: { type: String, required: true },
description: { type: String, required: true },
userId: { type: Schema.Types.ObjectId, required: true },
contactId: { type: Schema.Types.ObjectId, required: true },
imageURI: { type: String, required: true, unique: true },
objectKey: { type: String, required: true, unique: true },
created: { type: Date, default: Date.now }
});


module.exports = mongoose.model('picture', pictureSchema);
98 changes: 98 additions & 0 deletions lab-nathan/model/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
'use strict';

const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const crypto = require('crypto');
const createError = require('http-errors');
const jwt = require('jsonwebtoken');
const debug = require('debug')('cf-rolodex:user');

const Schema = mongoose.Schema;

const userSchema = Schema({
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
findHash: { type: String, unique: true }
});

let User;

userSchema.methods.generatePasswordHash = function(password) {
debug('generatePasswordHash');

return new Promise((resolve, reject) => {
bcrypt.hash(password, 10, (error, hash) => {
if (error) return reject(error);
this.password = hash;
resolve(this);
});
});
};

userSchema.methods.comparePasswordHash = function(password) {
debug('comparePasswordHash');

return new Promise((resolve, reject) => {
bcrypt.compare(password, this.password, (error, valid) => {
if (error) return reject(error);
if (!valid) return reject(createError(401, 'invalid password'));
resolve(this);
});
});
};

userSchema.methods.generateFindHash = function() {
debug('generateFindHash');

return new Promise((resolve, reject) => {
let tries = 0;
_generateFindHash.call(this);

function _generateFindHash() {
this.findHash = crypto.randomBytes(32).toString('hex');
this.save()
.then(() => {
return resolve(this.findHash);
})
.catch(error => {
if (tries > 3) {
return reject(error);
}

tries++;
_generateFindHash.call(this);
});
}
});
};

userSchema.methods.generateToken = function() {
debug('generateToken');

return new Promise((resolve, reject) => {
this.generateFindHash()
.then(findHash => resolve(jwt.sign({ token: findHash }, process.env.APP_SECRET)))
.catch(error => reject(error));
});
};

userSchema.statics.createAuthenticated = function(userData, callback) {
debug('createAuthenticated');
new User(userData).generatePasswordHash(userData.password)
.then(user => user.save())
.then(user => {
user.generateToken()
.then(token => {
callback(null, user, token);
return Promise.resolve();
})
.catch(error => {
callback(error);
return Promise.resolve();
});
})
.catch(callback);
};

module.exports = User = mongoose.model('user', userSchema);
36 changes: 36 additions & 0 deletions lab-nathan/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "lab-nathan",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test-win": "SET DEBUG=cf-rolodex* && istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage",
"test": "DEBUG='cf-rolodex*' && istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage",
"start-win": "SET DEBUG=cf-rolodex* && node server.js",
"start": "DEBUG='cf-rolodex*' && node server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"aws-sdk": "^2.95.0",
"bcrypt": "^1.0.2",
"body-parser": "^1.17.2",
"cors": "^2.8.4",
"del": "^3.0.0",
"dotenv": "^4.0.0",
"express": "^4.15.4",
"jsonwebtoken": "^7.4.2",
"mongoose": "^4.11.6",
"morgan": "^1.8.2",
"multer": "^1.3.0",
"uuid": "^3.1.0"
},
"devDependencies": {
"chai": "^4.1.1",
"coveralls": "^2.13.1",
"istanbul": "^0.4.5",
"mocha": "^3.5.0",
"superagent": "^3.5.2"
}
}
Loading