diff --git a/package.json b/package.json index 1c0a5d4f..189c3c3f 100644 --- a/package.json +++ b/package.json @@ -123,6 +123,7 @@ "passport": "^0.3.2", "passport-github": "^1.1.0", "passport-google-oauth20": "^1.0.0", + "passport-http": "^0.3.0", "passport-local": "^1.0.0", "pretty-bytes": "^3.0.1", "primer-tooltips": "^1.5.11", diff --git a/server/config/passport.js b/server/config/passport.js index 17975c02..1fb0013e 100644 --- a/server/config/passport.js +++ b/server/config/passport.js @@ -1,14 +1,15 @@ import slugify from 'slugify'; import friendlyWords from 'friendly-words'; +import lodash from 'lodash'; + +import passport from 'passport'; +import GitHubStrategy from 'passport-github'; +import LocalStrategy from 'passport-local'; +import GoogleStrategy from 'passport-google-oauth20'; +import { BasicStrategy } from 'passport-http'; + import User from '../models/user'; -const lodash = require('lodash'); -const passport = require('passport'); -const GitHubStrategy = require('passport-github').Strategy; -const LocalStrategy = require('passport-local').Strategy; -const GoogleStrategy = require('passport-google-oauth20').Strategy; - - passport.serializeUser((user, done) => { done(null, user.id); }); @@ -38,6 +39,26 @@ passport.use(new LocalStrategy({ usernameField: 'email' }, (email, password, don .catch(err => done(null, false, { msg: err })); })); +/** + * Authentificate using Basic Auth (Username + Api Key) + */ +passport.use(new BasicStrategy((userid, key, done) => { + User.findOne({ username: userid }, (err, user) => { // eslint-disable-line consistent-return + if (err) { return done(err); } + if (!user) { return done(null, false); } + user.findMatchingKey(key, (innerErr, isMatch, keyID) => { + if (isMatch) { + User.update( + { 'apiKeys._id': keyID }, + { '$set': { 'apiKeys.$.lastUsedAt': Date.now() } } + ); + return done(null, user); + } + return done(null, false, { msg: 'Invalid username or API key' }); + }); + }); +})); + /* Input: [ diff --git a/server/models/user.js b/server/models/user.js index 29e9934f..7e3dab45 100644 --- a/server/models/user.js +++ b/server/models/user.js @@ -71,14 +71,14 @@ userSchema.pre('save', function checkPassword(next) { // eslint-disable-line con /** * API keys hash middleware */ -userSchema.pre('save', function checkApiKey(next) { +userSchema.pre('save', function checkApiKey(next) { // eslint-disable-line consistent-return const user = this; if (!user.isModified('apiKeys')) { return next(); } let hasNew = false; user.apiKeys.forEach((k) => { if (k.isNew) { hasNew = true; - bcrypt.genSalt(10, (err, salt) => { + bcrypt.genSalt(10, (err, salt) => { // eslint-disable-line consistent-return if (err) { return next(err); } bcrypt.hash(k.hashedKey, salt, null, (innerErr, hash) => { if (innerErr) { return next(innerErr); } @@ -110,6 +110,20 @@ userSchema.methods.comparePassword = function comparePassword(candidatePassword, }); }; +/** + * Helper method for validating a user's api key + */ +userSchema.methods.findMatchingKey = function findMatchingKey(candidateKey, cb) { + let foundOne = false; + this.apiKeys.forEach((k) => { + if (bcrypt.compareSync(candidateKey, k.hashedKey)) { + foundOne = true; + cb(null, true, k._id); + } + }); + if (!foundOne) cb('Matching API key not found !', false, null); +}; + userSchema.statics.findByMailOrName = function findByMailOrName(email) { const query = { $or: [{