Skip to content

Commit

Permalink
#294 SameSite None env and login improvements (#302)
Browse files Browse the repository at this point in the history
* #294 SameSite First pass

* #294 Fix session and use postgres as session store

* Use a default session db port

* #294 Regenerate session on login failure too
  • Loading branch information
tariqksoliman authored Jan 9, 2023
1 parent 6c93cd1 commit 2eac369
Show file tree
Hide file tree
Showing 10 changed files with 335 additions and 207 deletions.
198 changes: 110 additions & 88 deletions API/Backend/Users/routes/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,104 +153,118 @@ router.post("/signup", function (req, res, next) {
* User login
*/
router.post("/login", function (req, res) {
let MMGISUser = req.cookies.MMGISUser
? JSON.parse(req.cookies.MMGISUser)
: false;
let username = req.body.username || (MMGISUser ? MMGISUser.username : null);
clearLoginSession(req);

if (username == null) {
res.send({ status: "failure", message: "No username provided." });
return;
}
req.session.regenerate((err) => {
let MMGISUser = req.cookies.MMGISUser
? JSON.parse(req.cookies.MMGISUser)
: false;
let username = req.body.username || (MMGISUser ? MMGISUser.username : null);

User.findOne({
where: {
username: username,
},
attributes: ["id", "username", "email", "password", "permission"],
})
.then((user) => {
if (!user) {
res.send({
status: "failure",
message: "Invalid username or password.",
});
} else {
function pass(err, result, again) {
if (result) {
// Save the user's info in the session
req.session.user = user.username;
req.session.uid = user.id;
req.session.token = crypto.randomBytes(128).toString("hex");
req.session.permission = user.permission;
if (username == null) {
res.send({ status: "failure", message: "No username provided." });
return;
}

User.update(
{
token: req.session.token,
},
{
where: {
id: user.id,
username: user.username,
},
}
)
.then(() => {
res.send({
status: "success",
username: user.username,
User.findOne({
where: {
username: username,
},
attributes: ["id", "username", "email", "password", "permission"],
})
.then((user) => {
if (!user) {
res.send({
status: "failure",
message: "Invalid username or password.",
});
} else {
function pass(err, result, again) {
if (result) {
// Save the user's info in the session
req.session.user = user.username;
req.session.uid = user.id;
req.session.token = crypto.randomBytes(128).toString("hex");
req.session.permission = user.permission;

User.update(
{
token: req.session.token,
groups: getUserGroups(user.username, req.leadGroupName),
},
{
where: {
id: user.id,
username: user.username,
},
}
)
.then(() => {
req.session.save(() => {
res.send({
status: "success",
username: user.username,
token: req.session.token,
groups: getUserGroups(user.username, req.leadGroupName),
additional:
process.env.THIRD_PARTY_COOKIES === "true"
? `; SameSite=None;${
process.env.NODE_ENV === "production"
? " Secure"
: ""
}`
: "",
});
});
return null;
})
.catch((err) => {
res.send({ status: "failure", message: "Login failed." });
return null;
});
return null;
} else {
res.send({
status: "failure",
message: "Invalid username or password.",
});
return null;
}
}

if (req.body.useToken && MMGISUser) {
if (MMGISUser.token == null) {
res.send({ status: "failure", message: "Bad token." });
return null;
}
User.findOne({
where: {
username: MMGISUser.username,
token: MMGISUser.token,
},
})
.then((user) => {
if (!user) {
res.send({ status: "failure", message: "Bad token." });
} else {
pass(null, true, true);
}
return null;
})
.catch((err) => {
res.send({ status: "failure", message: "Login failed." });
return null;
res.send({ status: "failure", message: "Bad token." });
});
return null;
} else {
res.send({
status: "failure",
message: "Invalid username or password.",
});
return null;
bcrypt.compare(req.body.password, user.password, pass);
}
}

if (req.body.useToken && MMGISUser) {
if (MMGISUser.token == null) {
res.send({ status: "failure", message: "Bad token." });
return null;
}
User.findOne({
where: {
username: MMGISUser.username,
token: MMGISUser.token,
},
})
.then((user) => {
if (!user) {
res.send({ status: "failure", message: "Bad token." });
} else {
pass(null, true, true);
}
return null;
})
.catch((err) => {
res.send({ status: "failure", message: "Bad token." });
});
return null;
} else {
bcrypt.compare(req.body.password, user.password, pass);
}
return null;
}
return null;
})
.catch((err) => {
res.send({ status: "failure", message: "Bad token." });
});
})
.catch((err) => {
res.send({ status: "failure", message: "Bad token." });
});
});
return null;
});

Expand All @@ -259,10 +273,7 @@ router.post("/logout", function (req, res) {
? JSON.parse(req.cookies.MMGISUser)
: false;

req.session.user = "guest";
req.session.uid = null;
req.session.token = null;
req.session.permission = null;
clearLoginSession(req);

if (MMGISUser == false) {
res.send({ status: "failure", message: "No user." });
Expand All @@ -279,7 +290,11 @@ router.post("/logout", function (req, res) {
}
)
.then(() => {
res.send({ status: "success" });
req.session.save(() => {
req.session.regenerate((err) => {
res.send({ status: "success" });
});
});
return null;
})
.catch((err) => {
Expand All @@ -299,4 +314,11 @@ function getUserGroups(user, leadGroupName) {
return Object.keys(groups);
}

function clearLoginSession(req) {
req.session.user = "guest";
req.session.uid = null;
req.session.token = null;
req.session.permission = null;
}

module.exports = router;
4 changes: 4 additions & 0 deletions docs/pages/Setup/ENVs/ENVs.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ Sets the `Content-Security-Policy: frame-ancestors` header to allow the embeddin

Sets the `Content-Security-Policy: frame-src` header to allow the embedding iframes from external origins into MMGIS | string[] | default `null` | ex. FRAME_SRC='["http://localhost:8888"]'

#### `THIRD_PARTY_COOKIES=`

Sets "SameSite=None; Secure" on the login cookie. Useful when using AUTH=local as an iframe within a cross-origin page. | boolean | default `false`

#### `PUBLIC_URL=`

Set MMGIS to be deployed under a subpath. Use full and absolute paths only to the project's build directory. For example if serving at the subpath 'mmgis/' is desired, set PUBLIC_URL to 'https://{domain}/mmgis/build'. Changing PUBLIC_URL required a rebuild. | string | default `null` (domain root build '/build')
Expand Down
Loading

0 comments on commit 2eac369

Please sign in to comment.