Salt and hash passwords

This commit is contained in:
Shweelan Samson authored and Zaid Abdulla committed 2019-03-27 02:17:48 -07:00
1 parent 88ad9451e2
commit d5b194b9e8
13 files changed
+410 -83

No files matched your search

+116
View File
@@ -0,0 +1,116 @@
import { VnodeDOM, ClosureComponent, Component } from "mithril";
import * as notifications from "./notifications";
import { changePassword } from "./store";
import { m } from "./components";
const component: ClosureComponent = (): Component => {
return {
view: vnode => {
const onPasswordChange = vnode.attrs["onPasswordChange"];
const enforceAuth = !vnode.attrs["noAuth"];
const username = vnode.attrs["username"];
if (username) vnode.state["username"] = username;
const form = [
m(
"p",
m("label", { for: "username" }, "Username"),
m("input", {
name: "username",
type: "text",
value: vnode.state["username"],
disabled: !!username,
oninput: m.withAttr("value", v => (vnode.state["username"] = v)),
oncreate: _vnode => {
(_vnode.dom as HTMLSelectElement).focus();
}
})
)
];
let fields = {
newPassword: "New password",
confirmPassword: "Confirm password"
};
if (enforceAuth)
fields = Object.assign({ authPassword: "Your password" }, fields);
for (const [f, l] of Object.entries(fields)) {
form.push(
m(
"p",
m("label", { for: f }, l),
m("input", {
name: f,
type: "password",
value: vnode.state[f],
oninput: m.withAttr("value", v => (vnode.state[f] = v))
})
)
);
}
const submit = m(
"button.primary",
{
type: "submit"
},
"Change password"
) as VnodeDOM;
form.push(m(".actions-bar", submit));
const children = [
m("h1", "Change password"),
m(
"form",
{
onsubmit: e => {
e.redraw = false;
e.preventDefault();
if (
!vnode.state["username"] ||
!vnode.state["newPassword"] ||
(enforceAuth && !vnode.state["authPassword"])
) {
notifications.push("error", "Please fill all fields");
} else if (
vnode.state["newPassword"] !== vnode.state["confirmPassword"]
) {
notifications.push(
"error",
"Password confirm doesn't match new password"
);
} else {
(submit.dom as HTMLFormElement).disabled = true;
changePassword(
vnode.state["username"],
vnode.state["newPassword"],
vnode.state["authPassword"]
)
.then(() => {
notifications.push(
"success",
"Password updated successfully"
);
if (onPasswordChange) onPasswordChange();
(submit.dom as HTMLFormElement).disabled = false;
})
.catch(err => {
notifications.push("error", err.message);
(submit.dom as HTMLFormElement).disabled = false;
});
}
}
},
form
)
];
return m("div.put-form", children);
}
};
};
export default component;
+21 -1
View File
@@ -1,7 +1,9 @@
import { ClosureComponent, Component } from "mithril";
import { ClosureComponent, Component, Children } from "mithril";
import { m } from "./components";
import * as store from "./store";
import * as notifications from "./notifications";
import * as overlay from "./overlay";
import changePasswordComponent from "./change-password-component";
export function init(args): Promise<{}> {
return Promise.resolve(args);
@@ -63,6 +65,24 @@ export const component: ClosureComponent = (): Component => {
"Login"
)
)
),
m(
"a",
{
onclick: () => {
const cb = (): Children => {
const attrs = {
onPasswordChange: () => {
overlay.close(cb);
m.redraw();
}
};
return m(changePasswordComponent, attrs);
};
overlay.open(cb);
}
},
"Change password"
)
];
}
+2 -1
View File
@@ -6,7 +6,8 @@ const singular = {
presets: "preset",
provisions: "provision",
virtualParameters: "virtual parameter",
files: "file"
files: "file",
users: "user"
};
function createField(current, attr, focus): Children {
+15
View File
@@ -549,6 +549,21 @@ export function evaluateExpression(exp, obj): Expression {
return memoizedEvaluate(exp, obj, fulfillTimestamp);
}
export function changePassword(
username,
newPassword,
authPassword?
): Promise<void> {
const data = { newPassword };
if (authPassword) data["authPassword"] = authPassword;
return m.request({
method: "PUT",
url: `/api/users/${username}/password`,
background: true,
data
});
}
export function logIn(username, password): Promise<void> {
return m.request({
method: "POST",
+92 -54
View File
@@ -11,6 +11,7 @@ import * as overlay from "./overlay";
import * as smartQuery from "./smart-query";
import { map, parse, stringify } from "../lib/common/expression-parser";
import filterComponent from "./filter-component";
import changePasswordComponent from "./change-password-component";
import { Children, ClosureComponent, Component } from "mithril";
const PAGE_SIZE = config.ui.pageSize || 10;
@@ -20,7 +21,6 @@ const memoizedJsonParse = memoize(JSON.parse);
const attributes = [
{ id: "_id", label: "Username" },
{ id: "password", label: "Password", type: "password", unsortable: true },
{
id: "roles",
label: "Roles",
@@ -47,19 +47,27 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
const object = Object.assign({}, _object);
if (action === "save") {
const id = object["_id"];
const password = object["password"];
const confirm = object["confirm"];
delete object["_id"];
delete object["password"];
delete object["confirm"];
if (!id) return void resolve({ _id: "ID can not be empty" });
if (isNew) {
if (!password) {
return void resolve({ password: "Password can not be empty" });
} else if (password !== confirm) {
return void resolve({
confirm: "Confirm password doesn't match password"
});
}
}
if (!Array.isArray(object.roles) || !object.roles.length)
return void resolve({ roles: "Role(s) must be selected" });
if (typeof object.password !== "string" || !object.password.length) {
if (isNew)
return void resolve({ password: "Password can not be empty" });
else delete object.password;
}
object.roles = object.roles.join(",");
store
@@ -78,12 +86,20 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
store
.putResource("users", id, object)
.then(() => {
notifications.push(
"success",
`User ${exists ? "updated" : "created"}`
);
store.fulfill(0, Date.now());
resolve();
if (isNew) {
store
.changePassword(id, password)
.then(() => {
notifications.push("success", "User created");
store.fulfill(0, Date.now());
resolve();
})
.catch(reject);
} else {
notifications.push("success", "User updated");
store.fulfill(0, Date.now());
resolve();
}
})
.catch(reject);
})
@@ -106,15 +122,9 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
});
}
const formData = {
resource: "users",
attributes: attributes
};
const getDownloadUrl = memoize(filter => {
const cols = {};
for (const attr of attributes)
if (attr.id !== "password") cols[attr.label] = attr.id;
for (const attr of attributes) cols[attr.label] = attr.id;
return `/api/users.csv?${m.buildQueryString({
filter: stringify(filter),
@@ -158,8 +168,6 @@ function renderTable(
const labels = [m("th", selectAll)];
for (const attr of attributes) {
if (attr.id === "password") continue;
const label = attr.label;
if (attr.unsortable) {
@@ -187,6 +195,8 @@ function renderTable(
labels.push(m("th", [label, sortable]));
}
const canChangePasswords = window.authorizer.hasAccess("users", 3);
const rows = [];
for (const user of users) {
const checkbox = m("input", {
@@ -203,8 +213,7 @@ function renderTable(
});
const tds = [m("td", checkbox)];
for (const attr of attributes)
if (attr.id !== "password") tds.push(m("td", user[attr.id]));
for (const attr of attributes) tds.push(m("td", user[attr.id]));
tds.push(
m(
@@ -214,39 +223,59 @@ function renderTable(
{
onclick: () => {
const cb = (): Children => {
return m(
putFormComponent,
Object.assign(
{
base: {
_id: user._id,
roles: user.roles.split(",")
const children = [
m(
putFormComponent,
Object.assign(
{
base: {
_id: user._id,
roles: user.roles.split(",")
},
actionHandler: (action, object) => {
return new Promise(resolve => {
putActionHandler(action, object, false)
.then(errors => {
const errorList = errors
? Object.values(errors)
: [];
if (errorList.length) {
for (const err of errorList)
notifications.push("error", err);
} else {
overlay.close(cb);
}
resolve();
})
.catch(err => {
notifications.push("error", err.message);
resolve();
});
});
}
},
actionHandler: (action, object) => {
return new Promise(resolve => {
putActionHandler(action, object, false)
.then(errors => {
const errorList = errors
? Object.values(errors)
: [];
if (errorList.length) {
for (const err of errorList)
notifications.push("error", err);
} else {
overlay.close(cb);
}
resolve();
})
.catch(err => {
notifications.push("error", err.message);
resolve();
});
});
{
resource: "users",
attributes: attributes
}
},
formData
)
)
);
];
if (canChangePasswords) {
children.push(m("hr"));
const attrs = {
noAuth: true,
username: user._id,
onPasswordChange: () => {
overlay.close(cb);
m.redraw();
}
};
children.push(m(changePasswordComponent, attrs));
}
return children;
};
overlay.open(cb);
}
@@ -331,6 +360,15 @@ function renderTable(
];
if (window.authorizer.hasAccess("users", 3)) {
const formData = {
resource: "users",
attributes: [
attributes[0],
{ id: "password", label: "Password", type: "password" },
{ id: "confirm", label: "Confirm password", type: "password" },
attributes[1]
]
};
buttons.push(
m(
"button.primary",