diff --git a/auth/auth.js b/auth/auth.js new file mode 100644 index 00000000..f00907be --- /dev/null +++ b/auth/auth.js @@ -0,0 +1,77 @@ +const jwt = require("jwt-simple"); +const frappe = require("frappejs"); +const passport = require("passport"); +const passportJWT = require("passport-jwt"); +const jwtSecret = require('crypto').randomBytes(256); + +const ExtractJwt = passportJWT.ExtractJwt; +const Strategy = passportJWT.Strategy; + +const params = { + secretOrKey: jwtSecret, + jwtFromRequest: ExtractJwt.fromHeader('token') +}; + + +module.exports = () => { + + const strategy = new Strategy(params, async function (payload, done) { + const email = payload.email; + if (!email) return done(new Error("Invalid Request"), null) + + const user = (await frappe.db.getAll({ + doctype: 'User', + filters: { name: email } + }))[0]; + + if (user) { + return done(null, { + email: user.email + }); + } else { + return done(new Error("User not found"), null); + } + }); + + passport.use(strategy); + + return { + initialize: () => { + return passport.initialize(); + }, + authenticate: () => { + return passport.authenticate("jwt", { session: false }); + }, + login: async function (req, res) { + if (req.body.email && req.body.password) { + const name = req.body.email || req.body.name; + const password = req.body.password; + + const user = (await frappe.db.getAll({ + doctype: 'User', + filters: { password, name } + }))[0]; + + if (user) { + const payload = { + email: user.name, + exp: timeInSecondsAfterHr(24) + }; + const token = jwt.encode(payload, jwtSecret); + res.json({ + token: token + }); + } else { + res.sendStatus(401); + } + + } else { + res.sendStatus(401); + } + } + }; +}; + +function timeInSecondsAfterHr(hour=1) { + return Math.floor(Date.now() / 1000) + (3600 * hour) +} \ No newline at end of file diff --git a/backends/http.js b/backends/http.js index 242d434f..48494933 100644 --- a/backends/http.js +++ b/backends/http.js @@ -108,10 +108,14 @@ module.exports = class HTTPClient extends Observable { } getHeaders() { - return { + const headers = { 'Accept': 'application/json', 'Content-Type': 'application/json' - } + }; + if (frappe.auth && frappe.auth.token) { + headers.token = frappe.auth.token; + }; + return headers; } initTypeMap() { diff --git a/models/doctype/User/User.js b/models/doctype/User/User.js index a0f9e437..674bbf10 100644 --- a/models/doctype/User/User.js +++ b/models/doctype/User/User.js @@ -5,17 +5,23 @@ module.exports = { "isChild": 0, "keywordFields": [ "name", - "full_name" + "fullName" ], "fields": [ { "fieldname": "name", - "label": "Name", + "label": "Email", "fieldtype": "Data", "required": 1 }, { - "fieldname": "full_name", + "fieldname": "password", + "label": "Password", + "fieldtype": "Password", + "required": 1 + }, + { + "fieldname": "fullName", "label": "Full Name", "fieldtype": "Data", "required": 1 @@ -25,6 +31,12 @@ module.exports = { "label": "Roles", "fieldtype": "Table", "childtype": "UserRole" + }, + { + "fieldname": "userId", + "label": "User ID", + "fieldtype": "Data", + "hidden": 1 } ] } \ No newline at end of file diff --git a/package.json b/package.json index 998af9ea..c61a3a1d 100644 --- a/package.json +++ b/package.json @@ -24,15 +24,19 @@ "frappejs": "../frappejs", "jquery": "^3.3.1", "luxon": "^1.0.0", + "jwt-simple": "^0.5.1", "mkdirp": "^0.5.1", "mocha": "^4.1.0", "moment": "^2.20.1", + "morgan": "^1.9.0", "mysql": "^2.15.0", "node-fetch": "^1.7.3", "node-sass": "^4.7.2", "nodemon": "^1.14.7", "nunjucks": "^3.1.0", "octicons": "^7.2.0", + "passport": "^0.4.0", + "passport-jwt": "^4.0.0", "popper.js": "^1.12.9", "precss": "^2.0.0", "puppeteer": "^1.2.0", diff --git a/server/index.js b/server/index.js index c734d9e5..cc0ddce4 100644 --- a/server/index.js +++ b/server/index.js @@ -13,14 +13,15 @@ const common = require('frappejs/common'); const bodyParser = require('body-parser'); const fs = require('fs'); const { setupExpressRoute: setRouteForPDF } = require('frappejs/server/pdf'); +const auth = require('./../auth/auth')(); +const morgan = require('morgan') require.extensions['.html'] = function (module, filename) { module.exports = fs.readFileSync(filename, 'utf8'); }; module.exports = { - async start({backend, connectionParams, models, staticPath = './'}) { - + async start({backend, connectionParams, models, staticPath = './', authConfig=null}) { await this.init(); if (models) { @@ -34,6 +35,13 @@ module.exports = { app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(staticPath)); + app.use(morgan('tiny')); + + if(authConfig) { + app.post("/api/login", auth.login); + app.use(auth.initialize(authConfig)); + app.all("/api/resource/*", auth.authenticate()); + } // socketio io.on('connection', function (socket) {