-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Initial Express.js setup for Ethereum cryptographic user auth
- Loading branch information
Showing
18 changed files
with
1,788 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
MNENOMIC = // Your metamask's recovery words | ||
INFURA_API_KEY_RINKEBY = | ||
INFURA_API_KEY_RINKEBY = | ||
NODE_ENV = development |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
const express = require('express'); | ||
const bodyParser = require('body-parser'); | ||
const authMiddleware = require('./middleware/auth'); | ||
|
||
const app = express(); | ||
|
||
const usersRouter = require('./routes/users'); | ||
|
||
// Middleware Plugins | ||
app.use(bodyParser.json()); // allow JSON uploads | ||
app.use(bodyParser.urlencoded({ extended: true })); // allow Form submissions | ||
// app.use(authMiddleware.initialize); | ||
app.use('/users', usersRouter); | ||
app.use((err, req, res, next) => { | ||
if (err.name === 'UnauthorizedError') { | ||
res.status(401).send('Invalid token'); | ||
} else { | ||
next(err); | ||
} | ||
}); | ||
|
||
// Routes | ||
app.get('/', (req, res) => { | ||
res.status(404).json({ | ||
message: 'Error: Server under development' | ||
}); | ||
}) | ||
|
||
module.exports = app; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
'use strict'; | ||
|
||
const _ = require('lodash'); | ||
const fs = require('fs'); | ||
|
||
fs.createReadStream('.env-sample') | ||
.pipe(fs.createWriteStream('../.env')); | ||
|
||
const dotenv = require('dotenv'); | ||
dotenv.config(); | ||
|
||
const config = { | ||
dev: 'development', | ||
test: 'testing', | ||
prod: 'production', | ||
port: process.env.PORT || 7000 | ||
}; | ||
|
||
// Check if script prefix provided (i.e. `NODE_ENV=development nodemon server.js`) | ||
// console.log(process.env.NODE_ENV); | ||
|
||
// Setup Node environment based on .env file else use default from hash | ||
process.env.NODE_ENV = process.env.NODE_ENV || config.dev; | ||
config.env = process.env.NODE_ENV; | ||
|
||
let envConfig; | ||
try { | ||
envConfig = require('./' + config.env); | ||
// Fallback to empty object if file does not exist | ||
envConfig = envConfig || {}; | ||
} catch(err) { | ||
envConfig = {}; | ||
console.error('Error reading .env file'); | ||
} | ||
|
||
// Merge configs so envConfig overwrites the config object | ||
module.exports = _.merge(config, envConfig); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
module.exports = { | ||
logging: true, | ||
db: { | ||
url: 'mongodb://localhost/datahighway' | ||
}, | ||
port: 7000 | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module.exports = { | ||
logging: false | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
module.exports = { | ||
logging: false, | ||
db: { | ||
url: 'mongodb://localhost/datahighway-test' | ||
}, | ||
port: 7111 | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
const User = require('../models/User'); | ||
const Account = require('../models/Account'); | ||
|
||
// GET index - | ||
const userList = (req, res) => { | ||
User.find() | ||
.populate('account') | ||
.then(users => { | ||
res.body = users; | ||
console.log('Authorised: User list returned in response'); | ||
res.json({ data: users }); | ||
}) | ||
.catch(error => res.status(500).json({ error: error.message })) | ||
}; | ||
|
||
// GET show - nonce | ||
const userShowNonce = (req, res) => { | ||
console.log('userShowNonce with req.query.network', req.query.network); | ||
console.log('userShowNonce with req.query.publicAddress', req.query.publicAddress); | ||
Account.findOne({ | ||
// FIXME - change this so it finds an account with both the given 'network' and 'publicAddress' | ||
// network: req.query.network, | ||
publicAddress: req.query.publicAddress | ||
}) | ||
.then(account => { | ||
const nonce = account.nonce; | ||
console.log('Authorised: User account public address nonce returned: ', nonce); | ||
res.json({ nonce: nonce }); | ||
}) | ||
.catch(error => res.status(500).json({ error: error.message })) | ||
}; | ||
|
||
// POST create | ||
const userCreate = (req, res) => { | ||
User.create(req.body) | ||
.then((user) => { | ||
res.status(201).json(user).end(); | ||
}) | ||
.catch(error => res.json({ error })) | ||
}; | ||
|
||
module.exports = { | ||
userList: userList, | ||
userShowNonce: userShowNonce, | ||
userCreate: userCreate | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
const passport = require('passport'); | ||
const JWT = require('jsonwebtoken'); | ||
const PassportJwt = require('passport-jwt'); | ||
const User = require('../models/User'); | ||
const Account = require('../models/Account'); | ||
|
||
const JWT_SECRET = 'xyz'; | ||
const JWT_ALGORITHM = 'HS256'; | ||
const JWT_EXPIRES_IN = '7 days'; | ||
|
||
// Use "createStrategy" instead of "authenticate". | ||
// See https://github.com/saintedlama/passport-local-mongoose | ||
passport.use(User.createStrategy()); | ||
|
||
// Middleware for Passport Authentication | ||
const register = async (req, res, next) => { | ||
console.log('Middleware for Passport Registration'); | ||
// Create new User model | ||
const user = new User({ | ||
email: req.body.email, | ||
name: req.body.name | ||
}); | ||
|
||
const newAccount = new Account({ | ||
network: req.body.network, | ||
publicAddress: req.body.publicAddress, | ||
nonce: '0' | ||
}); | ||
|
||
// FIXME - associate newAccount with user | ||
// const filter = { email: req.body.email }; | ||
// const update = { accounts: [newAccount] } | ||
// let doc = await User.findOneAndUpdate(filter, update, { | ||
// new: true, | ||
// useFindAndModify: true, | ||
// upsert: true | ||
// }); | ||
// await doc.save(); | ||
|
||
// Pass the User model to the Passport `register` method | ||
User.register(user, req.body.password, (error, user) => { | ||
if (error) { | ||
console.error('Error registering user with middleware: ', error); | ||
next(error); | ||
return; | ||
} | ||
console.log('Success registering user with middleware: ', user); | ||
// Store user so we can access in our handler | ||
req.user = user; | ||
next(); | ||
}) | ||
} | ||
|
||
const jwtOptions = { | ||
// Authorization: Bearer in request headers | ||
jwtFromRequest: PassportJwt.ExtractJwt.fromAuthHeaderAsBearerToken(), | ||
secretOrKey: JWT_SECRET, | ||
// Algorithms used to sign in | ||
algorithms: [JWT_ALGORITHM] | ||
} | ||
|
||
// Passport JWT Strategy triggered by validateJWTWithPassportJWT | ||
// https://www.npmjs.com/package/passport-jwt | ||
passport.use(new PassportJwt.Strategy(jwtOptions, | ||
// Post-Verified token - https://www.npmjs.com/package/passport-jwt | ||
(jwtPayload, done) => { | ||
console.log('PassportJwt Strategy being processed'); | ||
// Find user in MongoDB using the `id` in the JWT | ||
User.findById(jwtPayload.sub) | ||
// User.findById(jwtPayload._doc._id) | ||
.then((user) => { | ||
if (user) { | ||
done(null, user); | ||
} else { | ||
done(null, false); | ||
} | ||
}) | ||
.catch((error) => { | ||
done(error, false); | ||
}) | ||
} | ||
)) | ||
|
||
const validateJWTManually = (req, res, next) => { | ||
// Extract token without "JWT " or "Bearer " prefix | ||
const token = req.headers.authorization ? req.headers.authorization.split(" ")[1] : null; | ||
if (token) { | ||
// https://github.com/auth0/node-jsonwebtoken | ||
JWT.verify(token, JWT_SECRET, function(error, decodedToken) { | ||
if (error) { | ||
res.status(401).json({ | ||
message: 'Error: Token invalid' | ||
}); | ||
console.error('Error: Token invalid: ', error); | ||
next(error); | ||
return; | ||
} else { | ||
req.user = decodedToken; | ||
User.find({ email: decodedToken.email }) | ||
.then((user) => { | ||
if (user) { | ||
console.log('Success authorising user with middleware: ', decodedToken); | ||
next(); | ||
} else { | ||
res.status(403).json({ | ||
message: 'Error: Token valid but user no longer exists in database' | ||
}); | ||
console.error('Error: Token valid but user no longer exists in database: ', error); | ||
next(error); | ||
return; | ||
} | ||
}) | ||
.catch((error) => { | ||
res.status(500).json({ | ||
message: 'Error: Token valid but error occurred retrieving user from database' | ||
}); | ||
console.error('Error: Token valid but error occurred retrieving user from database: ', error); | ||
next(error); | ||
return; | ||
}) | ||
} | ||
}); | ||
} else { | ||
res.status(401).json({ | ||
message: "Error: No Token provided" | ||
}); | ||
console.error('Error: No Token provided: ', error); | ||
next(error); | ||
return; | ||
} | ||
} | ||
|
||
// JWT signed token - http://jwt.io/ | ||
const signJWTForUser = (req, res) => { | ||
// Create signed JWT | ||
const token = JWT.sign( | ||
// payload | ||
{ | ||
email: req.body.email | ||
}, | ||
// secretOrPrivateKey - https://raymii.org/s/snippets/OpenSSL_Password_Generator.html | ||
JWT_SECRET, | ||
// options - https://github.com/auth0/node-jsonwebtoken | ||
{ | ||
subject: req.body.email.toString(), | ||
algorithm: JWT_ALGORITHM, | ||
expiresIn: JWT_EXPIRES_IN | ||
} | ||
) | ||
|
||
// Return token in response object | ||
res.json({ | ||
token: token | ||
}) | ||
} | ||
|
||
module.exports = { | ||
initialize: passport.initialize(), | ||
register: register, | ||
signIn: passport.authenticate('local', { session: false }), | ||
signJWTForUser: signJWTForUser, | ||
validateJWTManually: validateJWTManually | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
const mongoose = require('./init'); | ||
const Schema = mongoose.Schema; | ||
|
||
const AccountSchema = Schema({ | ||
publicAddress: String, | ||
nonce: String | ||
}); | ||
|
||
const Account = mongoose.models.Account || mongoose.model('Account', AccountSchema); | ||
|
||
module.exports = Account; |
Oops, something went wrong.