focus-budget-manager
) and, after creating package.json
with the npm init
command, run the following command: npm i --save express body-parser mongoose consign cors bcrypt jsonwebtoken morgan passport passport-jwt module-alias
req.body
property..gitignore
file in the project root folder. Write the following into it: /node_modules/
BudgetManagerAPI/config
file in the BudgetManagerAPI/config
index.js
and enter the following code into it: module.exports = { secret: 'budgetsecret', session: { session: false }, database: 'mongodb://127.0.0.1:27017/budgetmanager' }
127.0.0.1:27017
you can use localhost
. If you want, you can work with the MongoDB cloud database created, for example, by means of MLabs.User
model that will be used for JWT authentication. To do this, go to the BudgetManagerAPI/app
folder and create the models
directory in it, and the user.js
file in it. At the beginning of the file connect dependencies: const mongoose = require('mongoose'), bcrypt = require('bcrypt');
mongoose
package is needed here to create a User
model, the bcrypt
package tools will be used to hash user passwords. const Schema = mongoose.Schema({ username: { type: String, unique: true, required: true }, password: { type: String, required: true }, clients: [{}] });
User
data schema. Thanks to this description, the following data will be assigned to the user of our system:password
).clients
).user.js
, add the following code to it: // - Schema.pre('save', function (next) { const user = this; if (this.isModified('password') || this.isNew) { bcrypt.genSalt(10, (error, salt) => { if (error) return next(error); bcrypt.hash(user.password, salt, (error, hash) => { if (error) return next(error); user.password = hash; next(); }); }); } else { return next(); } });
Schema.methods.comparePassword = function (password, callback) { bcrypt.compare(password, this.password, (error, matches) => { if (error) return callback(error); callback(null, matches); }); };
User
model: mongoose.model('User', Schema);
User
model is ready, create a file passport.js
in the BudgetManagerAPI/config
folder. Let's start working on this file with connecting dependencies: const PassportJWT = require('passport-jwt'), ExtractJWT = PassportJWT.ExtractJwt, Strategy = PassportJWT.Strategy, config = require('./index.js'), models = require('@BudgetManager/app/setup');
mongoose
package is needed to work with the User
model, and passport-jwt
is needed for authentication. module.exports = (passport) => { const User = models.User; const parameters = { secretOrKey: config.secret, jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken() }; passport.use(new Strategy(parameters, (payload, done) => { User.findOne({ id: payload.id }, (error, user) => { if (error) return done(error, false); if (user) done(null, user); else done(null, false); }); })); }
User
model and find the user by performing a search on the JWT token received from the client.BudgetManagerAPI/config
folder, create a database.js
file that is responsible for working with the database. Add the following to this file: module.exports = (mongoose, config) => { const database = mongoose.connection; mongoose.Promise = Promise; mongoose.connect(config.database, { useMongoClient: true, promiseLibrary: global.Promise }); database.on('error', error => console.log(`Connection to BudgetManager database failed: ${error}`)); database.on('connected', () => console.log('Connected to BudgetManager database')); database.on('disconnected', () => console.log('Disconnected from BudgetManager database')); process.on('SIGINT', () => { database.close(() => { console.log('BudgetManager terminated, connection closed'); process.exit(0); }) }); };
mongoose
to use the standard Promise
object. If you do not do this, you may encounter warnings displayed in the console. Then we created a standard mongoose
connection.services
folder and open the index.js
file already in it. Add the following to it: require('module-alias/register'); const http = require('http'), BudgetManagerAPI = require('@BudgetManagerAPI'), BudgetManagerServer = http.Server(BudgetManagerAPI), BudgetManagerPORT = process.env.PORT || 3001, LOCAL = '0.0.0.0'; BudgetManagerServer.listen(BudgetManagerPORT, LOCAL, () => console.log(`BudgetManagerAPI running on ${BudgetManagerPORT}`));
module_alias
, which we will configure later (a step is optional, but this approach will help make the code cleaner). If you decide not to use the module_alias
package, then instead of @BudgetManagerAPI
you will need to write ./services/BudgetManagerAPI/config
.node services
command in the command line interpreter you use.BudgetManagerAPI/config
directory, create an app.js
file. First, let's connect the dependencies: const express = require('express'), app = express(), bodyParser = require('body-parser'), mongoose = require('mongoose'), morgan = require('morgan'), consign = require('consign'), cors = require('cors'), passport = require('passport'), passportConfig = require('./passport')(passport), jwt = require('jsonwebtoken'), config = require('./index.js'), database = require('./database')(mongoose, config);
passportConfig = require('./passport')(passport)
we import the configuration file for passport
, passing passport
as an argument, since the following command is available in passport.js
:passport
inside the passport.js
file without having to connect it.app.js
file, we start working with packages and set the secret key: app.use(express.static('.')); app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); app.use(morgan('dev')); app.use(cors()); app.use(passport.initialize()); app.set('budgetsecret', config.secret);
cors
package, you can do the following: app.use(function(req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); next(); });
package.json
and add to it, just before the dependencies
block, the following: "homepage": "https://github.com/gdomaradzki/focus-gestor-orcamentos#readme", "_moduleAliases": { "@root": ".", "@BudgetManager": "./services/BudgetManagerAPI", "@BudgetManagerModels":"./services/BudgetManagerAPI/app/models", "@BudgetManagerAPI":"./services/BudgetManagerAPI/config/app.js", "@config": "./services/BudgetManagerAPI/config/index.js" }, "dependencies": {
dependencies
block already exists in the file, so you only need to add homepage
and _moduleAliases
.@root
alias, the index.js
configuration file using the @config
alias, and so on.BudgetManagerAPI/app
folder and create a new setup
folder, and in it - the index.js
file. Add the following to it: const mongoose = require('mongoose'), UserModel = require('@BudgetManagerModels/user');; const models = { User: mongoose.model('User') } module.exports = models;
BudgetManagerAPI/app
to the BudgetManagerAPI/app
folder, create the api
directory in it, and in it the auth.js
file. We write the following in it: const mongoose = require('mongoose'), jwt = require('jsonwebtoken'), config = require('@config');
module_alias
module module_alias
we have made the code cleaner. Otherwise I would have to write something like this: config = require('./../../config);
const api = {}; api.login = (User) => (req, res) => { User.findOne({ username: req.body.username }, (error, user) => { if (error) throw error; if (!user) res.status(401).send({ success: false, message: 'Authentication failed. User not found.' }); else { user.comparePassword(req.body.password, (error, matches) => { if (matches && !error) { const token = jwt.sign({ user }, config.secret); res.json({ success: true, message: 'Token granted', token }); } else { res.status(401).send({ success: false, message: 'Authentication failed. Wrong password.' }); } }); } }); }
api
object, in which we save all the necessary methods. In the login
method, we first pass the User
argument, since we need a method to access the User
model, then we pass the arguments req
and res
.User
object that matches the username
. If the username is not recognized, we give an error, otherwise we check the password and token associated with the user.api
method that will receive and parse the token: api.verify = (headers) => { if (headers && headers.authorization) { const split = headers.authorization.split(' '); if (split.length === 2) return split[1]; else return null; } else return null; }
Authorization
header. After all these steps, we can finally export the api
object: module.exports = api;
services/BudgetManagerAPI/app
folder and create the routes
directory in it, in which we will create the auth.js
file with the following contents: const models = require('@BudgetManager/app/setup'); module.exports = (app) => { const api = app.BudgetManagerAPI.app.api.auth; app.route('/') .get((req, res) => res.send('Budget Manager API')); app.route('/api/v1/auth') .post(api.login(models.User)); }
app
object, so you can set routes. Here we set the constant api
, which we use to work with the auth.js
file in the api
folder. Here we set the default route, '/'
, when accessing which the string “Budget Manager API” is passed to the user. Right there we create the route '/api/v1/auth'
(for which the POST request is used). To serve this route, we use the login
method, passing the User
model as an argument.app.js
back to the app.js
file, which is located in the BudgetManagerAPI/config
folder and add the following (the app.set('budgetsecret', config.secret)
line app.set('budgetsecret', config.secret)
given as a guideline, you don’t need to add it to the file a second time): app.set('budgetsecret', config.secret); consign({ cwd: 'services' }) .include('BudgetManagerAPI/app/setup') .then('BudgetManagerAPI/app/api') .then('BudgetManagerAPI/app/routes') .into(app); module.exports = app;
setup
folder are downloaded, so that an instance of the model will be created first. Then we load the API methods, and finally the routes.BudgetManagerAPI/app/api
back to the BudgetManagerAPI/app/api
folder and create the user.js
file in it. Put the following code in it: const mongoose = require('mongoose'); const api = {}; api.setup = (User) => (req, res) => { const admin = new User({ username: 'admin', password: 'admin', clients: [] }); admin.save(error => { if (error) throw error; console.log('Admin account was succesfully set up'); res.json({ success: true }); }) }
setup
method allows you to create an administrator account for debugging purposes. In the finished application of this account should not be. api.index = (User, BudgetToken) => (req, res) => { const token = BudgetToken; if (token) { User.find({}, (error, users) => { if (error) throw error; res.status(200).json(users); }); } else return res.status(403).send({ success: false, message: 'Unauthorized' }); }
signup
method that will be needed later. It is designed to register new users: api.signup = (User) => (req, res) => { if (!req.body.username || !req.body.password) res.json({ success: false, message: 'Please, pass a username and password.' }); else { const newUser = new User({ username: req.body.username, password: req.body.password, clients: [] }); newUser.save((error) => { if (error) return res.status(400).json({ success: false, message: 'Username already exists.' }); res.json({ success: true, message: 'Account created successfully' }); }) } } module.exports = api;
username
and password
fields are filled in, and if so, then, provided that a valid username is entered, a new user is created.user.js
file in the BudgetManagerAPI/app/routes
folder and write the following code into it: const passport = require('passport'), config = require('@config'), models = require('@BudgetManager/app/setup'); module.exports = (app) => { const api = app.BudgetManagerAPI.app.api.user; app.route('/api/v1/setup') .post(api.setup(models.User)) app.route('/api/v1/users') .get(passport.authenticate('jwt', config.session), api.index(models.User, app.get('budgetsecret'))); app.route('/api/v1/signup') .post(api.signup(models.User)); }
passport
library to organize the authentication, connect the configuration file to configure the session parameters, and connect the models so that you can check whether the user has the right to work with the API endpoints.route
, which can be accessed at http: // localhost: 3001 / api / v1 / auth .user
routes by going to http: // localhost: 3001 / api / v1 / users . The server will report a GET method with a status of 401. This indicates that the request was not processed because we do not have enough privileges to work with the target resource. The client will display a page with the text “Unauthorized”.setup
endpoint to create an administrator account. In the Postman interface, it will look like this:http://localhost:3001/api/v1/setup
, change the type of request to POST
and click the Send
button. The server's JSON response must contain the message "success": true
.http://localhost:3001/api/v1/auth
, on the Body
tab, specify the username
and password
keys with the same admin
value and click the Send
button.token
key, use the GET request, enter http://localhost:3001/api/v1/users
in the address field, then add a new Authorization
header on the Headers
tab with a value of the form Bearer token
(instead of token
insert the token copied from the previously received server response). In the same place, add a Content-Type
header with the value application/x-www-form-urlencoded
and click Send
.signup
.http://localhost:3001/api/v1/signup
, on the Body
tab, select the x-www-form-urlencoded
switch, enter the username
and password
keys with values ​​different from admin
, and click Send
. If everything works as it should, the following should come back:Postman
tab, which accessed http://localhost:3001/api/v1/users
to get the list of users, and click Send
, an array of two objects representing the administrator and the new user should come back.Source: https://habr.com/ru/post/340750/
All Articles