diff --git a/config/config.template.json b/config/config.template.json index 971873f..e276693 100644 --- a/config/config.template.json +++ b/config/config.template.json @@ -1,3 +1,10 @@ { - "basedn": "dc=example,dc=com" + "basedn": "dc=example,dc=com", + "sessionSecretKey": "super secret key", + "sessionCookie": { + "path": "/", + "httpOnly": true, + "secure": false, + "maxAge": 7200000 + } } \ No newline at end of file diff --git a/package.json b/package.json index 318affc..1aaf305 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "cookie-parser": "^1.4.6", "cors": "^2.8.5", "express": "^4.18.2", + "express-session": "^1.17.3", "ldapjs": "^3.0.5", "minimist": "^1.2.8", "morgan": "^1.10.0" @@ -20,13 +21,7 @@ "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-n": "^16.0.1", - "eslint-plugin-promise": "^6.1.1", - "body-parser": "^1.20.1", - "cookie": "^0.5.0", - "cookie-parser": "^1.4.6", - "express": "^4.18.2", - "minimist": "^1.2.8", - "morgan": "^1.10.0" + "eslint-plugin-promise": "^6.1.1" }, "scripts": { "lint": "DEBUG=eslint:cli-engine eslint --fix ." diff --git a/src/ldap.js b/src/ldap.js index 8bc3dee..3f093cd 100644 --- a/src/ldap.js +++ b/src/ldap.js @@ -16,18 +16,11 @@ export default class LDAP { this.#groupsdn = `ou=groups,${basedn}`; } - createUserBind (uid, password) { - return { - dn: `uid=${uid},${this.#peopledn}`, - password - }; + async bindUser (uid, password) { + return await this.#client.bind(`uid=${uid},${this.#peopledn}`, password); } - async getAllUsers (bind) { - const bindResult = await this.#client.bind(bind.dn, bind.password); - if (!bindResult.ok) { - return bindResult; - } + async getAllUsers () { const result = await this.#client.search(this.#peopledn, { scope: "one" }); @@ -35,12 +28,7 @@ export default class LDAP { return result; } - async addUser (bind, uid, attrs) { - const logger = new LDAP_MULTIOP_LOGGER(`add ${uid}`); - const bindResult = await this.#client.bind(bind.dn, bind.password, logger); - if (!bindResult.ok) { - return bindResult; - } + async addUser (uid, attrs) { const userDN = `uid=${uid},${this.#peopledn}`; if (!attrs.cn || !attrs.sn || !attrs.userPassword) { return { @@ -59,26 +47,17 @@ export default class LDAP { uid, userPassword: attrs.userPassword }; - await this.#client.add(userDN, entry, logger); - return logger; + return await this.#client.add(userDN, entry); } - async getUser (bind, uid) { - const bindResult = await this.#client.bind(bind.dn, bind.password); - if (!bindResult.ok) { - return bindResult; - } + async getUser (uid) { const result = await this.#client.search(`uid=${uid},${this.#peopledn}`, {}); result.user = result.entries[0]; // assume there should only be 1 entry return result; } - async modUser (bind, uid, newAttrs) { + async modUser (uid, newAttrs) { const logger = new LDAP_MULTIOP_LOGGER(`modify ${uid}`); - const bindResult = await this.#client.bind(bind.dn, bind.password, logger); - if (!bindResult.ok) { - return bindResult; - } for (const attr of ["cn", "sn", "userPassword"]) { if (attr in newAttrs && newAttrs[attr]) { // attr should exist and not be undefined or null const change = new ldap.Change({ @@ -94,12 +73,8 @@ export default class LDAP { return logger; } - async delUser (bind, uid) { + async delUser (uid) { const logger = new LDAP_MULTIOP_LOGGER(`del ${uid}`); - const bindResult = await this.#client.bind(bind.dn, bind.password, logger); - if (!bindResult.ok) { - return bindResult; - } const userDN = `uid=${uid},${this.#peopledn}`; await this.#client.del(userDN, logger); const groups = await this.#client.search(this.#groupsdn, { @@ -122,11 +97,7 @@ export default class LDAP { return logger; } - async getAllGroups (bind) { - const bindResult = await this.#client.bind(bind.dn, bind.password); - if (!bindResult.ok) { - return bindResult; - } + async getAllGroups () { const result = await this.#client.search(this.#groupsdn, { scope: "one" }); @@ -134,49 +105,28 @@ export default class LDAP { return result; } - async addGroup (bind, gid) { - const logger = new LDAP_MULTIOP_LOGGER(`add ${gid}`); - const bindResult = await this.#client.bind(bind.dn, bind.password, logger); - if (!bindResult.ok) { - return bindResult; - } + async addGroup (gid) { const groupDN = `cn=${gid},${this.#groupsdn}`; const entry = { objectClass: "groupOfNames", member: "", cn: gid }; - await this.#client.add(groupDN, entry, logger); - return logger; + return await this.#client.add(groupDN, entry); } - async getGroup (bind, gid) { - const bindResult = await this.#client.bind(bind.dn, bind.password); - if (!bindResult.ok) { - return bindResult; - } + async getGroup (gid) { const result = await this.#client.search(`cn=${gid},${this.#groupsdn}`, {}); result.group = result.entries[0]; // assume there should only be 1 entry return result; } - async delGroup (bind, gid) { - const logger = new LDAP_MULTIOP_LOGGER(`del ${gid}`); - const bindResult = await this.#client.bind(bind.dn, bind.password, logger); - if (!bindResult.ok) { - return bindResult; - } + async delGroup (gid) { const groupDN = `cn=${gid},${this.#groupsdn}`; - await this.#client.del(groupDN, logger); - return logger; + return await this.#client.del(groupDN); } - async addUserToGroup (bind, uid, gid) { - const logger = new LDAP_MULTIOP_LOGGER(`add ${uid} to ${gid}`); - const bindResult = await this.#client.bind(bind.dn, bind.password, logger); - if (!bindResult.ok) { - return bindResult; - } + async addUserToGroup (uid, gid) { // add the user const change = new ldap.Change({ operation: "add", @@ -185,16 +135,10 @@ export default class LDAP { values: [`uid=${uid},${this.#peopledn}`] } }); - await this.#client.modify(`cn=${gid},${this.#groupsdn}`, change, logger); - return logger; + return await this.#client.modify(`cn=${gid},${this.#groupsdn}`, change); } - async delUserFromGroup (bind, uid, gid) { - const logger = new LDAP_MULTIOP_LOGGER(`del ${uid} from ${gid}`); - const bindResult = await this.#client.bind(bind.dn, bind.password, logger); - if (!bindResult.ok) { - return bindResult; - } + async delUserFromGroup (uid, gid) { const change = new ldap.Change({ operation: "delete", modification: { @@ -202,8 +146,7 @@ export default class LDAP { values: [`uid=${uid},${this.#peopledn}`] } }); - await this.#client.modify(`cn=${gid},${this.#groupsdn}`, change, logger); - return logger; + return await this.#client.modify(`cn=${gid},${this.#groupsdn}`, change); } } diff --git a/src/main.js b/src/main.js index 3d7a0ee..fe719d2 100644 --- a/src/main.js +++ b/src/main.js @@ -2,6 +2,7 @@ import express from "express"; import bodyParser from "body-parser"; import cookieParser from "cookie-parser"; import morgan from "morgan"; +import session from "express-session"; import LDAP from "./ldap.js"; import _config from "./config.js"; @@ -21,12 +22,19 @@ global.argv = parseArgs(process.argv.slice(2), { global.package = _package(global.argv.package); global.config = _config(global.argv.configPath); -const ldap = new LDAP(global.argv.ldapURL, global.config.basedn); +const LDAPSessions = {}; const app = express(); app.use(bodyParser.urlencoded({ extended: true })); app.use(cookieParser()); app.use(morgan("combined")); +app.use(session({ + secret: global.config.sessionSecretKey, + name: global.config.sessionCookieName, + cookie: global.config.sessionCookie, + resave: false, + saveUninitialized: true +})); app.listen(global.argv.listenPort, () => { console.log(`proxmoxaas-api v${global.package.version} listening on port ${global.argv.listenPort}`); @@ -50,16 +58,53 @@ app.get("/echo", (req, res) => { res.status(200).send({ body: req.body, cookies: req.cookies }); }); -app.get("/users", async (req, res) => { +/** + * POST - get session ticket by authenticating using user id and password + */ +app.post("/ticket", async (req, res) => { const params = { - bind: ldap.createUserBind(req.body.binduser, req.body.bindpass) + uid: req.body.uid, + password: req.body.password }; - const result = await ldap.getAllUsers(params.bind); - res.send({ - ok: result.ok, - error: result.error, - users: result.users - }); + const newLDAPSession = new LDAP(global.argv.ldapURL, global.config.basedn); + const bindResult = await newLDAPSession.bindUser(params.uid, params.password); + if (bindResult.ok) { + LDAPSessions[req.session.id] = newLDAPSession; + res.status(200).send({ auth: true }); + } + else { + res.send({ + ok: bindResult.ok, + error: bindResult.error + }); + } +}); + +/** + * DELETE - invalidate and remove session ticket + */ +app.delete("/ticket", async (req, res) => { + req.session.ldap = null; + req.session.destroy(); + res.send({ auth: false }); +}); + +/** + * GET - get user attributes for all users + */ +app.get("/users", async (req, res) => { + if (req.session.id in LDAPSessions) { + const ldap = LDAPSessions[req.session.id]; + const result = await ldap.getAllUsers(); + res.send({ + ok: result.ok, + error: result.error, + users: result.users + }); + } + else { + res.status(403).send({ auth: false }); + } }); /** @@ -69,39 +114,42 @@ app.get("/users", async (req, res) => { * - cn: common name * - sn: surname * - userpassword: user password - * - binduser: bind user id - * - bindpass: bind user password */ app.post("/users/:userid", async (req, res) => { const params = { userid: req.params.userid, - bind: ldap.createUserBind(req.body.binduser, req.body.bindpass), userattrs: { cn: req.body.usercn, sn: req.body.usersn, userPassword: req.body.userpassword } }; - const checkUser = await ldap.getUser(params.bind, params.userid); - if (!checkUser.ok && checkUser.error.code === 32) { // the user does not exist, create new user - const result = await ldap.addUser(params.bind, params.userid, params.userattrs); - res.send({ - ok: result.ok, - error: result.error - }); + if (req.session.id in LDAPSessions) { + const ldap = LDAPSessions[req.session.id]; + const checkUser = await ldap.getUser(params.userid); + if (!checkUser.ok && checkUser.error.code === 32) { // the user does not exist, create new user + const result = await ldap.addUser(params.userid, params.userattrs); + res.send({ + ok: result.ok, + error: result.error + }); + } + else if (checkUser.ok) { // the user does exist, modify the user entries + const result = await ldap.modUser(params.userid, params.userattrs); + res.send({ + ok: result.ok, + error: result.error + }); + } + else { // some other error happened + res.send({ + ok: checkUser.ok, + error: checkUser.error + }); + } } - else if (checkUser.ok) { // the user does exist, modify the user entries - const result = await ldap.modUser(params.bind, params.userid, params.userattrs); - res.send({ - ok: result.ok, - error: result.error - }); - } - else { // some other error happened - res.send({ - ok: checkUser.ok, - error: checkUser.error - }); + else { + res.status(403).send({ auth: false }); } }); @@ -109,27 +157,30 @@ app.post("/users/:userid", async (req, res) => { * GET - get user attributes * request: * - userid: user id - * - binduser: bind user id - * - bindpass: bind user password */ app.get("/users/:userid", async (req, res) => { const params = { - userid: req.params.userid, - bind: ldap.createUserBind(req.body.binduser, req.body.bindpass) + userid: req.params.userid }; - const result = await ldap.getUser(params.bind, params.userid); - if (result.ok) { - res.send({ - ok: result.ok, - error: result.error, - user: result.user - }); + if (req.session.id in LDAPSessions) { + const ldap = LDAPSessions[req.session.id]; + const result = await ldap.getUser(params.userid); + if (result.ok) { + res.send({ + ok: result.ok, + error: result.error, + user: result.user + }); + } + else { + res.send({ + ok: result.ok, + error: result.error + }); + } } else { - res.send({ - ok: result.ok, - error: result.error - }); + res.status(403).send({ auth: false }); } }); @@ -137,77 +188,93 @@ app.get("/users/:userid", async (req, res) => { * DELETE - delete user * request: * - userid: user id - * - binduser: bind user id - * - bindpass: bind user password */ app.delete("/users/:userid", async (req, res) => { const params = { - userid: req.params.userid, - bind: ldap.createUserBind(req.body.binduser, req.body.bindpass) + userid: req.params.userid }; - const result = await ldap.delUser(params.bind, params.userid); - res.send({ - ok: result.ok, - error: result.error - }); + if (req.session.id in LDAPSessions) { + const ldap = LDAPSessions[req.session.id]; + const result = await ldap.delUser(params.userid); + res.send({ + ok: result.ok, + error: result.error + }); + } + else { + res.status(403).send({ auth: false }); + } }); +/** + * GET - get group attributes including members for all groups + * request: + */ app.get("/groups", async (req, res) => { - const params = { - bind: ldap.createUserBind(req.body.binduser, req.body.bindpass) - }; - const result = await ldap.getAllGroups(params.bind); - res.send({ - ok: result.ok, - error: result.error, - groups: result.groups - }); + if (req.session.id in LDAPSessions) { + const ldap = LDAPSessions[req.session.id]; + const result = await ldap.getAllGroups(); + res.send({ + ok: result.ok, + error: result.error, + groups: result.groups + }); + } + else { + res.status(403).send({ auth: false }); + } }); /** * POST - create a new group * request: * - groupid: group id - * - binduser: bind user id - * - bindpass: bind user password */ app.post("/groups/:groupid", async (req, res) => { const params = { - groupid: req.params.groupid, - bind: ldap.createUserBind(req.body.binduser, req.body.bindpass) + groupid: req.params.groupid }; - const result = await ldap.addGroup(params.bind, params.groupid); - res.send({ - ok: result.ok, - error: result.error - }); + if (req.session.id in LDAPSessions) { + const ldap = LDAPSessions[req.session.id]; + const result = await ldap.addGroup(params.groupid); + res.send({ + ok: result.ok, + error: result.error + }); + } + else { + res.status(403).send({ auth: false }); + } }); /** * GET - get group attributes including members * request: * - groupid: group id - * - binduser: bind user id - * - bindpass: bind user password */ app.get("/groups/:groupid", async (req, res) => { const params = { - groupid: req.params.groupid, - bind: ldap.createUserBind(req.body.binduser, req.body.bindpass) + groupid: req.params.groupid }; - const result = await ldap.getGroup(params.bind, params.groupid); - if (result.ok) { - res.send({ - ok: result.ok, - error: result.error, - group: result.group - }); + if (req.session.id in LDAPSessions) { + const ldap = LDAPSessions[req.session.id]; + const result = await ldap.getGroup(params.groupid); + if (result.ok) { + res.send({ + ok: result.ok, + error: result.error, + group: result.group + }); + } + else { + res.send({ + ok: result.ok, + error: result.error + }); + } } else { - res.send({ - ok: result.ok, - error: result.error - }); + res.status(403).send({ auth: false }); } }); @@ -215,19 +282,22 @@ app.get("/groups/:groupid", async (req, res) => { * DELETE - delete group * request: * - groupid: group id - * - binduser: bind user id - * - bindpass: bind user password */ app.delete("/groups/:groupid", async (req, res) => { const params = { - groupid: req.params.groupid, - bind: ldap.createUserBind(req.body.binduser, req.body.bindpass) + groupid: req.params.groupid }; - const result = await ldap.delGroup(params.bind, params.groupid); - res.send({ - ok: result.ok, - error: result.error - }); + if (req.session.id in LDAPSessions) { + const ldap = LDAPSessions[req.session.id]; + const result = await ldap.delGroup(params.groupid); + res.send({ + ok: result.ok, + error: result.error + }); + } + else { + res.status(403).send({ auth: false }); + } }); /** @@ -235,38 +305,44 @@ app.delete("/groups/:groupid", async (req, res) => { * request: * - groupid: group id * - userid: user id - * - binduser: bind user id - * - bindpass: bind user password */ app.post("/groups/:groupid/members/:userid", async (req, res) => { const params = { groupid: req.params.groupid, - userid: req.params.userid, - bind: ldap.createUserBind(req.body.binduser, req.body.bindpass) + userid: req.params.userid }; - const result = await ldap.addUserToGroup(params.bind, params.userid, params.groupid); - res.send({ - ok: result.ok, - error: result.error - }); + if (req.session.id in LDAPSessions) { + const ldap = LDAPSessions[req.session.id]; + const result = await ldap.addUserToGroup(params.userid, params.groupid); + res.send({ + ok: result.ok, + error: result.error + }); + } + else { + res.status(403).send({ auth: false }); + } }); /** * DELETE - remove a member from the group * - groupid: group id * - userid: user id - * - binduser: bind user id - * - bindpass: bind user password */ app.delete("/groups/:groupid/members/:userid", async (req, res) => { const params = { groupid: req.params.groupid, - userid: req.params.userid, - bind: ldap.createUserBind(req.body.binduser, req.body.bindpass) + userid: req.params.userid }; - const result = await ldap.delUserFromGroup(params.bind, params.userid, params.groupid); - res.send({ - ok: result.ok, - error: result.error - }); + if (req.session.id in LDAPSessions) { + const ldap = LDAPSessions[req.session.id]; + const result = await ldap.delUserFromGroup(params.userid, params.groupid); + res.send({ + ok: result.ok, + error: result.error + }); + } + else { + res.status(403).send({ auth: false }); + } });