mirror of
https://github.com/filecoin-project/slate.git
synced 2024-11-23 14:07:20 +03:00
reset password: loading states for edit account and sign in, adds reset password, adds validations to protect against light malice
This commit is contained in:
parent
02ef7d71a6
commit
d11fe19bab
28
common/validations.js
Normal file
28
common/validations.js
Normal file
@ -0,0 +1,28 @@
|
||||
import * as Strings from "~/common/strings";
|
||||
|
||||
const USERNAME_REGEX = new RegExp("^[a-zA-Z0-9_]{0,}[a-zA-Z]+[0-9]*$");
|
||||
const MIN_PASSWORD_LENGTH = 8;
|
||||
|
||||
export const username = (text) => {
|
||||
if (Strings.isEmpty(text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!USERNAME_REGEX.test(text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const password = (text) => {
|
||||
if (Strings.isEmpty(text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (text.length < MIN_PASSWORD_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
@ -58,10 +58,6 @@ const STYLES_BUTTON_PRIMARY = css`
|
||||
`;
|
||||
|
||||
export const ButtonPrimary = (props) => {
|
||||
if (props.type === "label") {
|
||||
return <label css={STYLES_BUTTON_PRIMARY} {...props} />;
|
||||
}
|
||||
|
||||
if (props.loading) {
|
||||
return (
|
||||
<button css={STYLES_BUTTON_PRIMARY} style={props.style}>
|
||||
@ -70,6 +66,19 @@ export const ButtonPrimary = (props) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (props.type === "label") {
|
||||
return (
|
||||
<label
|
||||
css={STYLES_BUTTON_PRIMARY}
|
||||
style={props.style}
|
||||
onClick={props.onClick}
|
||||
children={props.children}
|
||||
type={props.label}
|
||||
htmlFor={props.htmlFor}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
css={STYLES_BUTTON_PRIMARY}
|
||||
@ -100,10 +109,6 @@ const STYLES_BUTTON_PRIMARY_FULL = css`
|
||||
`;
|
||||
|
||||
export const ButtonPrimaryFull = (props) => {
|
||||
if (props.type === "label") {
|
||||
return <label css={STYLES_BUTTON_PRIMARY_FULL} {...props} />;
|
||||
}
|
||||
|
||||
if (props.loading) {
|
||||
return (
|
||||
<button css={STYLES_BUTTON_PRIMARY_FULL} style={props.style}>
|
||||
@ -112,6 +117,19 @@ export const ButtonPrimaryFull = (props) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (props.type === "label") {
|
||||
return (
|
||||
<label
|
||||
css={STYLES_BUTTON_PRIMARY_FULL}
|
||||
style={props.style}
|
||||
onClick={props.onClick}
|
||||
children={props.children}
|
||||
type={props.label}
|
||||
htmlFor={props.htmlFor}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
css={STYLES_BUTTON_PRIMARY_FULL}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { runQuery } from "~/node_common/data/utilities";
|
||||
|
||||
export default async ({ id, data, username }) => {
|
||||
export default async ({ id, data, username, salt, password }) => {
|
||||
const updateObject = {};
|
||||
|
||||
if (data) {
|
||||
@ -11,6 +11,14 @@ export default async ({ id, data, username }) => {
|
||||
updateObject.username = username;
|
||||
}
|
||||
|
||||
if (salt) {
|
||||
updateObject.salt = salt;
|
||||
}
|
||||
|
||||
if (password) {
|
||||
updateObject.password = password;
|
||||
}
|
||||
|
||||
return await runQuery({
|
||||
label: "UPDATE_USER_BY_ID",
|
||||
queryFn: async (DB) => {
|
||||
|
@ -12,6 +12,7 @@ const initCORS = MW.init(MW.CORS);
|
||||
export default async (req, res) => {
|
||||
initCORS(req, res);
|
||||
|
||||
// NOTE(jim): We don't need to validate here.
|
||||
if (Strings.isEmpty(req.body.data.username)) {
|
||||
return res.status(500).send({ decorator: "SERVER_SIGN_IN", error: true });
|
||||
}
|
||||
@ -46,6 +47,9 @@ export default async (req, res) => {
|
||||
Environment.LOCAL_PASSWORD_SECRET
|
||||
);
|
||||
|
||||
console.log(phaseThree);
|
||||
console.log(user.password);
|
||||
|
||||
if (phaseThree !== user.password) {
|
||||
return res
|
||||
.status(403)
|
||||
|
@ -1,8 +1,8 @@
|
||||
import * as Environment from "~/node_common/environment";
|
||||
import * as MW from "~/node_common/middleware";
|
||||
import * as Data from "~/node_common/data";
|
||||
import * as Strings from "~/common/strings";
|
||||
import * as Environment from "~/node_common/environment";
|
||||
import * as Utilities from "~/node_common/utilities";
|
||||
import * as Validations from "~/common/validations";
|
||||
|
||||
import PG from "~/node_common/powergate";
|
||||
import JWT from "jsonwebtoken";
|
||||
@ -25,12 +25,16 @@ export default async (req, res) => {
|
||||
.json({ decorator: "SERVER_EXISTING_USER_ALREADY", error: true });
|
||||
}
|
||||
|
||||
if (Strings.isEmpty(req.body.data.username)) {
|
||||
return res.status(500).send({ error: "A username was not provided." });
|
||||
if (!Validations.username(req.body.data.username)) {
|
||||
return res
|
||||
.status(500)
|
||||
.send({ decorator: "SERVER_INVALID_USERNAME", error: true });
|
||||
}
|
||||
|
||||
if (Strings.isEmpty(req.body.data.password)) {
|
||||
return res.status(500).send({ error: "A password was not provided." });
|
||||
if (!Validations.password(req.body.data.password)) {
|
||||
return res
|
||||
.status(500)
|
||||
.send({ decorator: "SERVER_INVALID_PASSWORD", error: true });
|
||||
}
|
||||
|
||||
// TODO(jim): Do not expose how many times you are salting
|
||||
|
@ -2,7 +2,6 @@ import * as Environment from "~/node_common/environment";
|
||||
import * as MW from "~/node_common/middleware";
|
||||
import * as Data from "~/node_common/data";
|
||||
import * as Utilities from "~/node_common/utilities";
|
||||
import * as Strings from "~/common/strings";
|
||||
|
||||
import { Buckets } from "@textile/hub";
|
||||
import { Libp2pCryptoIdentity } from "@textile/threads-core";
|
||||
|
@ -1,10 +1,12 @@
|
||||
import * as Environment from "~/node_common/environment";
|
||||
import * as MW from "~/node_common/middleware";
|
||||
import * as Data from "~/node_common/data";
|
||||
import * as Utilities from "~/node_common/utilities";
|
||||
import * as Strings from "~/common/strings";
|
||||
import * as Validations from "~/common/validations";
|
||||
|
||||
import DB from "~/node_common/database";
|
||||
import PG from "~/node_common/powergate";
|
||||
import BCrypt from "bcrypt";
|
||||
|
||||
const initCORS = MW.init(MW.CORS);
|
||||
const initAuth = MW.init(MW.RequireCookieAuthentication);
|
||||
@ -26,13 +28,13 @@ export default async (req, res) => {
|
||||
|
||||
if (!user) {
|
||||
return res
|
||||
.status(200)
|
||||
.status(500)
|
||||
.json({ decorator: "SERVER_USER_UPDATE", error: true });
|
||||
}
|
||||
|
||||
if (user.error) {
|
||||
return res
|
||||
.status(200)
|
||||
.status(500)
|
||||
.json({ decorator: "SERVER_USER_UPDATE", error: true });
|
||||
}
|
||||
|
||||
@ -56,6 +58,27 @@ export default async (req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(jim): Do not expose how many times you are salting
|
||||
// in OSS, add a random value as an environment variable.
|
||||
if (req.body.type == "CHANGE_PASSWORD") {
|
||||
if (!Validations.password(req.body.password)) {
|
||||
return res
|
||||
.status(500)
|
||||
.json({ decorator: "SERVER_INVALID_PASSWORD", error: true });
|
||||
}
|
||||
|
||||
const salt = await BCrypt.genSalt(13);
|
||||
const hash = await BCrypt.hash(req.body.password, salt);
|
||||
const double = await BCrypt.hash(hash, salt);
|
||||
const triple = await BCrypt.hash(double, Environment.LOCAL_PASSWORD_SECRET);
|
||||
|
||||
await Data.updateUserById({
|
||||
id: user.id,
|
||||
salt,
|
||||
password: triple,
|
||||
});
|
||||
}
|
||||
|
||||
// TODO(jim): POWERGATE_ISSUE 0.2.0
|
||||
// Should work when our hosted Powergate works.
|
||||
if (req.body.type === "SET_DEFAULT_STORAGE_CONFIG") {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as React from "react";
|
||||
import * as System from "~/components/system";
|
||||
import * as Actions from "~/common/actions";
|
||||
import * as Validations from "~/common/validations";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
|
||||
@ -25,9 +26,18 @@ const delay = (time) =>
|
||||
);
|
||||
|
||||
export default class SceneEditAccount extends React.Component {
|
||||
state = { username: this.props.viewer.username, deleting: false };
|
||||
state = {
|
||||
username: this.props.viewer.username,
|
||||
password: "",
|
||||
confirm: "",
|
||||
deleting: false,
|
||||
changingPassword: false,
|
||||
changingUsername: false,
|
||||
changingAvatar: false,
|
||||
};
|
||||
|
||||
_handleUpload = async (e) => {
|
||||
this.setState({ changingAvatar: true });
|
||||
e.persist();
|
||||
let file = e.target.files[0];
|
||||
|
||||
@ -53,15 +63,48 @@ export default class SceneEditAccount extends React.Component {
|
||||
data: { photo: `https://hub.textile.io${json.data.ipfs}` },
|
||||
});
|
||||
|
||||
this.props.onRehydrate();
|
||||
await this.props.onRehydrate();
|
||||
|
||||
this.setState({ changingAvatar: false });
|
||||
};
|
||||
|
||||
_handleSave = async (e) => {
|
||||
this.setState({ changingUsername: true });
|
||||
|
||||
if (!Validations.username(this.state.username)) {
|
||||
alert("TODO: Not a valid username");
|
||||
this.setState({ changingUsername: false });
|
||||
return;
|
||||
}
|
||||
|
||||
await Actions.updateViewer({
|
||||
username: this.state.username,
|
||||
});
|
||||
|
||||
this.props.onRehydrate();
|
||||
await this.props.onRehydrate();
|
||||
this.setState({ changingUsername: false });
|
||||
};
|
||||
|
||||
_handleChangePassword = async (e) => {
|
||||
this.setState({ changingPassword: true });
|
||||
if (this.state.password !== this.state.confirm) {
|
||||
alert("TODO: Error message for non-matching passwords");
|
||||
this.setState({ changingPassword: false });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Validations.password(this.state.password)) {
|
||||
alert("TODO: Not a valid password");
|
||||
this.setState({ changingPassword: false });
|
||||
return;
|
||||
}
|
||||
|
||||
await Actions.updateViewer({
|
||||
type: "CHANGE_PASSWORD",
|
||||
password: this.state.password,
|
||||
});
|
||||
|
||||
this.setState({ changingPassword: false, password: "", confirm: "" });
|
||||
};
|
||||
|
||||
_handleDelete = async (e) => {
|
||||
@ -110,8 +153,9 @@ export default class SceneEditAccount extends React.Component {
|
||||
style={{ margin: "0 16px 16px 0" }}
|
||||
type="label"
|
||||
htmlFor="file"
|
||||
loading={this.state.changingAvatar}
|
||||
>
|
||||
Upload
|
||||
Pick avatar
|
||||
</System.ButtonPrimary>
|
||||
</div>
|
||||
|
||||
@ -134,8 +178,46 @@ export default class SceneEditAccount extends React.Component {
|
||||
/>
|
||||
|
||||
<div style={{ marginTop: 24 }}>
|
||||
<System.ButtonPrimary onClick={this._handleSave}>
|
||||
Save
|
||||
<System.ButtonPrimary
|
||||
onClick={this._handleSave}
|
||||
loading={this.state.changingUsername}
|
||||
>
|
||||
Change username
|
||||
</System.ButtonPrimary>
|
||||
</div>
|
||||
|
||||
<System.DescriptionGroup
|
||||
style={{ marginTop: 48 }}
|
||||
label="Reset password"
|
||||
description="Your new password must be a minimum of four characters."
|
||||
/>
|
||||
|
||||
<System.Input
|
||||
containerStyle={{ marginTop: 24 }}
|
||||
label="New password"
|
||||
name="password"
|
||||
type="password"
|
||||
value={this.state.password}
|
||||
placeholder="Your new password"
|
||||
onChange={this._handleChange}
|
||||
/>
|
||||
|
||||
<System.Input
|
||||
containerStyle={{ marginTop: 24 }}
|
||||
label="Confirm password"
|
||||
name="confirm"
|
||||
type="password"
|
||||
value={this.state.confirm}
|
||||
placeholder="Confirm it!"
|
||||
onChange={this._handleChange}
|
||||
/>
|
||||
|
||||
<div style={{ marginTop: 24 }}>
|
||||
<System.ButtonPrimary
|
||||
onClick={this._handleChangePassword}
|
||||
loading={this.state.changingPassword}
|
||||
>
|
||||
Change password
|
||||
</System.ButtonPrimary>
|
||||
</div>
|
||||
|
||||
|
@ -2,7 +2,7 @@ import * as React from "react";
|
||||
import * as Actions from "~/common/actions";
|
||||
import * as System from "~/components/system";
|
||||
import * as Constants from "~/common/constants";
|
||||
import * as Strings from "~/common/strings";
|
||||
import * as Validations from "~/common/validations";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
|
||||
@ -76,16 +76,21 @@ export default class SceneSignIn extends React.Component {
|
||||
|
||||
// TODO(jim):
|
||||
// Lets add some proper error messages here.
|
||||
if (Strings.isEmpty(this.state.username)) {
|
||||
alert("TODO: No username");
|
||||
if (!Validations.username(this.state.username)) {
|
||||
alert(
|
||||
"TODO: Your username was invalid, only characters and numbers allowed."
|
||||
);
|
||||
this.setState({ loading: false });
|
||||
return;
|
||||
}
|
||||
|
||||
if (Strings.isEmpty(this.state.password)) {
|
||||
alert("TODO: No password");
|
||||
this.setState({ loading: false });
|
||||
return;
|
||||
if (!Validations.password(this.state.password)) {
|
||||
alert("TODO: Your password must be at least 8 characters.");
|
||||
|
||||
// TODO(jim):
|
||||
// Let it slide because this rule is new.
|
||||
// this.setState({ loading: false });
|
||||
// return;
|
||||
}
|
||||
|
||||
const response = await this.props.onAuthenticate({
|
||||
@ -117,7 +122,8 @@ export default class SceneSignIn extends React.Component {
|
||||
Version {Constants.values.version}
|
||||
<br />
|
||||
Public Test Preview <br />
|
||||
Warning: Entire Network/Database Will Be Wiped
|
||||
Warning: THE Entire Network & Database Will Be Wiped
|
||||
<br />
|
||||
</div>
|
||||
|
||||
<System.Input
|
||||
@ -126,6 +132,10 @@ export default class SceneSignIn extends React.Component {
|
||||
value={this.state.username}
|
||||
onChange={this._handleChange}
|
||||
/>
|
||||
<div css={STYLES_CODE_PREVIEW} style={{ marginTop: 8 }}>
|
||||
Usernames should only have characters or numbers.
|
||||
</div>
|
||||
|
||||
<System.Input
|
||||
containerStyle={{ marginTop: 24 }}
|
||||
label="Password"
|
||||
@ -134,6 +144,10 @@ export default class SceneSignIn extends React.Component {
|
||||
value={this.state.password}
|
||||
onChange={this._handleChange}
|
||||
/>
|
||||
<div css={STYLES_CODE_PREVIEW} style={{ marginTop: 8 }}>
|
||||
Password should be at least 8 characters
|
||||
</div>
|
||||
|
||||
<System.ButtonPrimaryFull
|
||||
style={{ marginTop: 48 }}
|
||||
onClick={!this.state.loading ? this._handleSubmit : () => {}}
|
||||
|
Loading…
Reference in New Issue
Block a user