From f676de6c7cf5260aee1c4674838ba5c37ea2a545 Mon Sep 17 00:00:00 2001 From: zanisis Date: Tue, 25 Apr 2017 17:15:15 +0700 Subject: [PATCH] first API with Token --- .gitignore | 1 + README.md | 34 ++++- app.js | 50 ++++++++ bin/www | 90 ++++++++++++++ config/config.json | 23 ++++ controllers/users.js | 152 +++++++++++++++++++++++ migrations/20170425040220-create-user.js | 35 ++++++ models/index.js | 36 ++++++ models/user.js | 15 +++ package.json | 21 ++++ public/stylesheets/style.css | 8 ++ routes/index.js | 9 ++ routes/signin.js | 9 ++ routes/signup.js | 7 ++ routes/users.js | 17 +++ seeders/20170425040622-users.js | 30 +++++ views/error.ejs | 3 + views/index.ejs | 11 ++ 18 files changed, 550 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 app.js create mode 100755 bin/www create mode 100644 config/config.json create mode 100644 controllers/users.js create mode 100644 migrations/20170425040220-create-user.js create mode 100644 models/index.js create mode 100644 models/user.js create mode 100644 package.json create mode 100644 public/stylesheets/style.css create mode 100644 routes/index.js create mode 100644 routes/signin.js create mode 100644 routes/signup.js create mode 100644 routes/users.js create mode 100644 seeders/20170425040622-users.js create mode 100644 views/error.ejs create mode 100644 views/index.ejs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/README.md b/README.md index b2e3b8b..feebb65 100644 --- a/README.md +++ b/README.md @@ -1 +1,33 @@ -# api-auth \ No newline at end of file +# api-auth + +##Summary +Kita akan membuat layanan menggunakan REST pada aplikasi kita yang dilengkapi fitur otentikasi dan otorisasi pengguna, sehingga tidak sembarang orang yang dapat mengakses API yang telah kita buat. Aplikasi ini sebaiknya sudah menggunakan database. + +``` +Pertama yang kita butuh kan adalah : +$ express --veiw=ejs . \\jika ingin menggunakan engine ejs +$ npm install \ +$ npm install --save password-hash[secret password] jsonwebtoken[untuk get token API] pg[optional] +``` +Buatlah sebuah source data atau database: \ +\* Bisa menggunakan file JSON atau SQLITE/POSTGRES DLL. + +####Route API +Kamu bisa menggunakan pola perintah seperti dibawah ini : +``` +| Route | HTTP | Description | +|-----------------|---------|-----------------------------------------------------------| +| /api/signup | POST | Sign up with new user info | +| /api/signin | POST | Sign in while get an access token based on credentials | +| /api/users | GET | Get all the users info (admin only) | +| /api/users/:id | GET | Get a single user info (admin and authenticated user) | +| /api/users | POST | Create a user (admin only) | +| /api/users/:id | DELETE | Delete a user (admin only) | +| /api/users/:id | PUT | Update a user with new info (admin and authenticated user)| +``` + +####Example Usage + +``` +localhost:3000/api/users //will return all users in JSON format in Postman +``` \ No newline at end of file diff --git a/app.js b/app.js new file mode 100644 index 0000000..dbd6eab --- /dev/null +++ b/app.js @@ -0,0 +1,50 @@ +var express = require('express'); +var path = require('path'); +var favicon = require('serve-favicon'); +var logger = require('morgan'); +var cookieParser = require('cookie-parser'); +var bodyParser = require('body-parser'); + +var index = require('./routes/index'); +var users = require('./routes/users'); +var signup = require('./routes/signup'); +var signin = require('./routes/signin'); + +var app = express(); + +// view engine setup +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'ejs'); + +// uncomment after placing your favicon in /public +//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); +app.use(logger('dev')); +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: true })); +app.use(cookieParser()); +app.use(express.static(path.join(__dirname, 'public'))); + +app.use('/', index); +app.use('/api/users', users); +app.use('/api/signup', signup); +app.use('/api/signin', signin); + +// catch 404 and forward to error handler +app.use(function(req, res, next) { + var err = new Error('Not Found'); + err.status = 404; + next(err); +}); + +// error handler +app.use(function(err, req, res, next) { + // set locals, only providing error in development + res.locals.message = err.message; + res.locals.error = req.app.get('env') === 'development' ? err : {}; + + // render the error page + res.status(err.status || 500); + res.render('error'); +}); + +module.exports = app; diff --git a/bin/www b/bin/www new file mode 100755 index 0000000..fea87fd --- /dev/null +++ b/bin/www @@ -0,0 +1,90 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var app = require('../app'); +var debug = require('debug')('api-auth:server'); +var http = require('http'); + +/** + * Get port from environment and store in Express. + */ + +var port = normalizePort(process.env.PORT || '3000'); +app.set('port', port); + +/** + * Create HTTP server. + */ + +var server = http.createServer(app); + +/** + * Listen on provided port, on all network interfaces. + */ + +server.listen(port); +server.on('error', onError); +server.on('listening', onListening); + +/** + * Normalize a port into a number, string, or false. + */ + +function normalizePort(val) { + var port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ + +function onError(error) { + if (error.syscall !== 'listen') { + throw error; + } + + var bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } +} + +/** + * Event listener for HTTP server "listening" event. + */ + +function onListening() { + var addr = server.address(); + var bind = typeof addr === 'string' + ? 'pipe ' + addr + : 'port ' + addr.port; + debug('Listening on ' + bind); +} diff --git a/config/config.json b/config/config.json new file mode 100644 index 0000000..d2cf37d --- /dev/null +++ b/config/config.json @@ -0,0 +1,23 @@ +{ + "development": { + "username": "postgres", + "password": 12345, + "database": "apiOuth", + "host": "127.0.0.1", + "dialect": "postgres" + }, + "test": { + "username": "root", + "password": null, + "database": "database_test", + "host": "127.0.0.1", + "dialect": "mysql" + }, + "production": { + "username": "root", + "password": null, + "database": "database_production", + "host": "127.0.0.1", + "dialect": "mysql" + } +} diff --git a/controllers/users.js b/controllers/users.js new file mode 100644 index 0000000..99df4f4 --- /dev/null +++ b/controllers/users.js @@ -0,0 +1,152 @@ +const passwordHash = require('password-hash'); +const jwt = require('jsonwebtoken'); +const pass = require('../node_modules/password-hash/lib/password-hash'); +const db = require('../models'); + + +let controller = {} + +controller.signup = function (req, res, next) { + let hash = passwordHash.generate(req.body.password); + db.User.create({ + name : req.body.name, + password : hash, + role : req.body.role + }) + .then((user)=>{ + res.send(user) + }) + .catch((err)=>{ + res.send(err.message) + }) +} + +controller.signin = function (req, res, next) { + db.User.findOne({ + where : {name : req.body.name} + }) + .then((user)=>{ + let hashed = passwordHash.verify(req.body.password, user.password) + // res.send(hashed) + if(hashed){ + let token = jwt.sign({ + username: user.name, + role: user.role },'secret', { expiresIn: '1h' }) + res.send(token) + } else { + res.send('Password Salah') + } + }) + .catch((err)=>{ + res.send('Username Salah') + }) +} + +controller.findall = function(req, res, next) { + let decode = jwt.decode(req.headers.token) + jwt.verify(req.headers.token, 'secret', function(err,decode){ + if(err){ + res.send(err.name) + } else { + if(decode.role == 'admin'){ + db.User.findAll() + .then((users)=>{ + res.send(users) + }) + .catch((err)=>{ + res.send(err) + }) + } else { + res.send('You do not have permission') + } + } + }) + +} + +controller.find = function(req, res, next) { + let decode = jwt.decode(req.headers.token) + jwt.verify(req.headers.token, 'secret', (err, decode)=>{ + if(err){ + res.send(err.name) + } else { + db.User.findById(req.params.id) + .then((user)=>{ + res.send(user) + }) + .catch((err)=>{ + res.send(err) + }) + } + }) +} + +controller.create = function(req, res, next) { + let decode = jwt.decode(req.headers.token) + jwt.verify(req.headers.token, 'secret', (err, decode)=>{ + if(err){ + res.send(err.name) + } else if(decode.role == 'admin'){ + let hash = passwordHash.generate(req.body.password); + db.User.create({ + name : req.body.name, + password : hash, + role : req.body.role + }) + .then((users)=>{ + res.send(users) + }) + .catch(err =>{ + res.send(err) + }) + } else { + res.send(`You don't have permission`) + } + }) + +} + +controller.delete = function(req, res, next) { + let decode = jwt.decode(req.headers.token) + jwt.verify(req.headers.token, 'secret', (err, decode)=>{ + if(err){ + res.send(err.name) + } else if(decode.role == 'admin'){ + db.User.destroy({ + where : {id : req.params.id} + }) + .then((users)=>{ + res.send('done') + }) + .catch((err)=>{ + res.send(err) + }) + } else { + res.send(`You don't have permission`) + } + }) + +} + +controller.update = function(req, res, next) { + let decode = jwt.decode(req.headers.token) + jwt.verify(req.headers.token, 'secret', (err, decode)=>{ + if(err){ + res.send(err.name) + } else { + db.User.update({ + name : req.body.name, + email : req.body.email + },{where : {id : req.params.id}}) + .then((users)=>{ + res.send(users) + }) + .catch((err)=>{ + res.send(err) + }) + } + }) +} + + +module.exports = controller; \ No newline at end of file diff --git a/migrations/20170425040220-create-user.js b/migrations/20170425040220-create-user.js new file mode 100644 index 0000000..ad38a79 --- /dev/null +++ b/migrations/20170425040220-create-user.js @@ -0,0 +1,35 @@ +'use strict'; +module.exports = { + up: function(queryInterface, Sequelize) { + return queryInterface.createTable('Users', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + name: { + type: Sequelize.STRING + }, + password: { + type: Sequelize.STRING + }, + role: { + type: Sequelize.STRING + }, + createdAt: { + allowNull: false, + defaultValue: new Date(), + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + defaultValue: new Date(), + type: Sequelize.DATE + } + }); + }, + down: function(queryInterface, Sequelize) { + return queryInterface.dropTable('Users'); + } +}; \ No newline at end of file diff --git a/models/index.js b/models/index.js new file mode 100644 index 0000000..7540dba --- /dev/null +++ b/models/index.js @@ -0,0 +1,36 @@ +'use strict'; + +var fs = require('fs'); +var path = require('path'); +var Sequelize = require('sequelize'); +var basename = path.basename(module.filename); +var env = process.env.NODE_ENV || 'development'; +var config = require(__dirname + '/../config/config.json')[env]; +var db = {}; + +if (config.use_env_variable) { + var sequelize = new Sequelize(process.env[config.use_env_variable]); +} else { + var sequelize = new Sequelize(config.database, config.username, config.password, config); +} + +fs + .readdirSync(__dirname) + .filter(function(file) { + return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'); + }) + .forEach(function(file) { + var model = sequelize['import'](path.join(__dirname, file)); + db[model.name] = model; + }); + +Object.keys(db).forEach(function(modelName) { + if (db[modelName].associate) { + db[modelName].associate(db); + } +}); + +db.sequelize = sequelize; +db.Sequelize = Sequelize; + +module.exports = db; diff --git a/models/user.js b/models/user.js new file mode 100644 index 0000000..b2eaa7a --- /dev/null +++ b/models/user.js @@ -0,0 +1,15 @@ +'use strict'; +module.exports = function(sequelize, DataTypes) { + var User = sequelize.define('User', { + name: DataTypes.STRING, + password: DataTypes.STRING, + role: DataTypes.STRING + }, { + classMethods: { + associate: function(models) { + // associations can be defined here + } + } + }); + return User; +}; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..8389635 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "api-auth", + "version": "0.0.0", + "private": true, + "scripts": { + "start": "nodemon ./bin/www" + }, + "dependencies": { + "body-parser": "~1.17.1", + "cookie-parser": "~1.4.3", + "debug": "~2.6.3", + "ejs": "~2.5.6", + "express": "~4.15.2", + "jsonwebtoken": "^7.4.0", + "morgan": "~1.8.1", + "password-hash": "^1.2.2", + "pg": "^6.1.5", + "sequelize": "^3.30.4", + "serve-favicon": "~2.4.2" + } +} diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css new file mode 100644 index 0000000..9453385 --- /dev/null +++ b/public/stylesheets/style.css @@ -0,0 +1,8 @@ +body { + padding: 50px; + font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; +} + +a { + color: #00B7FF; +} diff --git a/routes/index.js b/routes/index.js new file mode 100644 index 0000000..ecca96a --- /dev/null +++ b/routes/index.js @@ -0,0 +1,9 @@ +var express = require('express'); +var router = express.Router(); + +/* GET home page. */ +router.get('/', function(req, res, next) { + res.render('index', { title: 'Express' }); +}); + +module.exports = router; diff --git a/routes/signin.js b/routes/signin.js new file mode 100644 index 0000000..bf30c19 --- /dev/null +++ b/routes/signin.js @@ -0,0 +1,9 @@ +var express = require('express'); +var router = express.Router(); +const controllers = require('../controllers/users'); + +router.post('/', controllers.signin) + + + +module.exports = router; diff --git a/routes/signup.js b/routes/signup.js new file mode 100644 index 0000000..6be1499 --- /dev/null +++ b/routes/signup.js @@ -0,0 +1,7 @@ +var express = require('express'); +var router = express.Router(); +const controllers = require('../controllers/users'); + +router.post('/', controllers.signup) + +module.exports = router; diff --git a/routes/users.js b/routes/users.js new file mode 100644 index 0000000..443d634 --- /dev/null +++ b/routes/users.js @@ -0,0 +1,17 @@ +var express = require('express'); +var router = express.Router(); +const controllers = require('../controllers/users'); + + +/* GET users listing. */ +// router.get('/', function(req, res, next) { +// res.send('respond with a resource'); +// }); + +router.get('/', controllers.findall); +router.get('/:id', controllers.find); +router.post('/', controllers.create); +router.delete('/:id', controllers.delete); +router.put('/:id', controllers.update); + +module.exports = router; diff --git a/seeders/20170425040622-users.js b/seeders/20170425040622-users.js new file mode 100644 index 0000000..c34dc38 --- /dev/null +++ b/seeders/20170425040622-users.js @@ -0,0 +1,30 @@ +'use strict'; + +module.exports = { + up: function (queryInterface, Sequelize) { + /* + Add altering commands here. + Return a promise to correctly handle asynchronicity. + + Example: + return queryInterface.bulkInsert('Person', [{ + name: 'John Doe', + isBetaMember: false + }], {}); + */ + return queryInterface.bulkInsert('Users', [{ + name: 'John Doe', + password: 'tuanjhondoe' + }], {}); + }, + + down: function (queryInterface, Sequelize) { + /* + Add reverting commands here. + Return a promise to correctly handle asynchronicity. + + Example: + return queryInterface.bulkDelete('Person', null, {}); + */ + } +}; diff --git a/views/error.ejs b/views/error.ejs new file mode 100644 index 0000000..7cf94ed --- /dev/null +++ b/views/error.ejs @@ -0,0 +1,3 @@ +

<%= message %>

+

<%= error.status %>

+
<%= error.stack %>
diff --git a/views/index.ejs b/views/index.ejs new file mode 100644 index 0000000..7b7a1d6 --- /dev/null +++ b/views/index.ejs @@ -0,0 +1,11 @@ + + + + <%= title %> + + + +

<%= title %>

+

Welcome to <%= title %>

+ +