- Fixed looking up invalid profiles
- Added valid error handling to bad request && rate limiting
- Add Sendgrid library (Will add SMTP later)
- Complete password reset process
This commit is contained in:
Daniel Mason 2021-04-02 01:56:08 +13:00
parent e570314ac2
commit fd615102a8
Signed by: idanoo
GPG key ID: 387387CDBC02F132
28 changed files with 871 additions and 261 deletions

View file

@ -32,9 +32,14 @@ export const PostLogin = (formValues) => {
toast.error(response.data.error ? response.data.error: 'An Unknown Error has occurred');
return null
}
})
.catch(() => {
toast.error('Failed to connect');
}).catch((error) => {
if (error.response === 401) {
return {};
} else if (error.response === 429) {
toast.error('Rate limited. Please try again shortly')
} else {
toast.error('Failed to connect');
}
return Promise.resolve();
});
};
@ -51,20 +56,70 @@ export const PostRegister = (formValues) => {
return Promise.reject();
}
})
.catch(() => {
}).catch((error) => {
if (error.response === 401) {
return {};
} else if (error.response === 429) {
toast.error('Rate limited. Please try again shortly')
} else {
toast.error('Failed to connect');
return Promise.resolve();
});
}
return Promise.resolve();
});
};
export const PostResetPassword = (formValues) => {
return axios.post(process.env.REACT_APP_API_URL + "resetpassword", formValues)
.then((response) => {
if (response.data.message) {
toast.success(response.data.message);
return Promise.resolve();
} else {
toast.error(response.data.error ? response.data.error: 'An Unknown Error has occurred');
return Promise.reject();
}
}).catch((error) => {
if (error.response === 401) {
return {};
} else if (error.response === 429) {
toast.error('Rate limited. Please try again shortly')
} else {
toast.error('Failed to connect');
}
return Promise.resolve();
});
};
export const sendPasswordReset = (values) => {
return axios.post(process.env.REACT_APP_API_URL + "sendreset", values).then(
(data) => {
return data.data;
}).catch((error) => {
if (error.response === 401) {
return {};
} else if (error.response === 429) {
toast.error('Rate limited. Please try again shortly')
} else {
toast.error('Failed to connect');
}
return {};
});
};
export const getStats = () => {
return axios.get(process.env.REACT_APP_API_URL + "stats").then(
(data) => {
return data.data;
}).catch((error) => {
if (error.response === 401) {
return {};
} else if (error.response === 429) {
toast.error('Rate limited. Please try again shortly')
} else {
toast.error('Failed to connect');
}
).catch(() => {
toast.error('Failed to connect');
return {};
});
};
@ -73,8 +128,14 @@ export const getRecentScrobbles = (id) => {
return axios.get(process.env.REACT_APP_API_URL + "user/" + id + "/scrobbles", { headers: getHeaders() })
.then((data) => {
return data.data;
}).catch(() => {
toast.error('Failed to connect');
}).catch((error) => {
if (error.response === 401) {
return {};
} else if (error.response === 429) {
toast.error('Rate limited. Please try again shortly')
} else {
toast.error('Failed to connect');
}
return {};
});
};
@ -83,8 +144,14 @@ export const getConfigs = () => {
return axios.get(process.env.REACT_APP_API_URL + "config", { headers: getHeaders() })
.then((data) => {
return data.data;
}).catch(() => {
toast.error('Failed to connect');
}).catch((error) => {
if (error.response === 401) {
return {};
} else if (error.response === 429) {
toast.error('Rate limited. Please try again shortly')
} else {
toast.error('Failed to connect');
}
return {};
});
};
@ -103,18 +170,30 @@ export const postConfigs = (values, toggle) => {
} else if (data.data && data.data.error) {
toast.error(data.data.error);
}
})
.catch(() => {
toast.error('Error updating values');
});
}).catch((error) => {
if (error.response === 401) {
return {};
} else if (error.response === 429) {
toast.error('Rate limited. Please try again shortly')
} else {
toast.error('Failed to connect');
}
return {};
});
};
export const getProfile = (userName) => {
return axios.get(process.env.REACT_APP_API_URL + "profile/" + userName, { headers: getHeaders() })
.then((data) => {
return data.data;
}).catch(() => {
toast.error('Failed to connect');
}).catch((error) => {
if (error.response === 401) {
return {};
} else if (error.response === 429) {
toast.error('Rate limited. Please try again shortly')
} else {
toast.error('Failed to connect');
}
return {};
});
};
@ -123,8 +202,35 @@ export const getUser = () => {
return axios.get(process.env.REACT_APP_API_URL + "user", { headers: getHeaders() })
.then((data) => {
return data.data;
}).catch(() => {
toast.error('Failed to connect');
}).catch((error) => {
if (error.response === 401) {
return {};
} else if (error.response === 429) {
toast.error('Rate limited. Please try again shortly')
} else {
toast.error('Failed to connect');
}
return {};
});
};
export const validateResetPassword = (tokenStr) => {
return axios.post(process.env.REACT_APP_API_URL + "resetpassword", { token: tokenStr })
.then((data) => {
if (data.error) {
toast.error(data.error);
return {valid: false}
}
return data.data;
}).catch((error) => {
if (error.response === 401) {
return {};
} else if (error.response === 429) {
toast.error('Rate limited. Please try again shortly')
} else {
toast.error('Failed to connect');
}
return {};
});
};

View file

@ -7,6 +7,7 @@ import User from './Pages/User';
import Admin from './Pages/Admin';
import Login from './Pages/Login';
import Register from './Pages/Register';
import Reset from './Pages/Reset';
import Navigation from './Components/Navigation';
@ -38,6 +39,9 @@ const App = () => {
<Route path="/login" component={Login} />
<Route path="/register" component={Register} />
<Route path="/reset/:token" component={Reset} />
<Route path="/reset" component={Reset} />
</Switch>
</div>
);

View file

@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import { toast } from 'react-toastify';
import AuthContext from './AuthContext';
import { PostLogin, PostRegister } from '../Api/index';
import { PostLogin, PostRegister, PostResetPassword } from '../Api/index';
const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState();
@ -30,11 +30,14 @@ const AuthContextProvider = ({ children }) => {
const Register = (formValues) => {
setLoading(true);
return PostRegister(formValues).then(response => {
// Do stuff here?
setLoading(false);
});
};
const ResetPassword = (formValues) => {
return PostResetPassword(formValues)
}
const Logout = () => {
localStorage.removeItem("user");
setUser(null)
@ -47,6 +50,7 @@ const AuthContextProvider = ({ children }) => {
Logout,
Login,
Register,
ResetPassword,
loading,
user,
}}

View file

@ -1,4 +1,5 @@
import React, { useContext, useState, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import '../App.css';
import './Login.css';
import { Button } from 'reactstrap';
@ -9,6 +10,7 @@ import { Switch } from 'formik-material-ui';
import { getConfigs, postConfigs } from '../Api/index'
const Admin = () => {
const history = useHistory();
const { user } = useContext(AuthContext);
const [loading, setLoading] = useState(true);
const [configs, setConfigs] = useState({})
@ -38,11 +40,7 @@ const Admin = () => {
}
if (!user || !user.admin) {
return (
<div className="pageWrapper">
<h1>Unauthorized</h1>
</div>
)
history.push("/login")
}
return (

View file

@ -8,7 +8,7 @@ import ScrobbleTable from "../Components/ScrobbleTable";
import AuthContext from '../Contexts/AuthContext';
const Dashboard = () => {
const history = useHistory();
// const history = useHistory();
let { user } = useContext(AuthContext);
let [loading, setLoading] = useState(true);
let [dashboardData, setDashboardData] = useState({});

View file

@ -16,6 +16,10 @@ const Login = () => {
history.push("/dashboard");
}
const redirectReset = () => {
history.push("/reset")
}
return (
<div className="pageWrapper">
<h1>
@ -42,7 +46,6 @@ const Login = () => {
<Field
name="password"
type="password"
required={boolTrue}
className="loginFields"
/>
</label>
@ -53,6 +56,14 @@ const Login = () => {
className="loginButton"
disabled={loading}
>{loading ? <ScaleLoader color="#FFF" size={35} /> : "Login"}</Button>
<br/><br/>
<Button
color="secondary"
type="button"
className="loginButton"
onClick={redirectReset}
disabled={loading}
>{loading ? <ScaleLoader color="#FFF" size={35} /> : "Reset Password"}</Button>
</Form>
</Formik>
</div>

View file

@ -35,7 +35,7 @@ const Profile = (route) => {
)
}
if (!username || Object.keys(profile).length === 0) {
if (!username || !profile.username) {
return (
<div className="pageWrapper">
Unable to fetch user
@ -50,7 +50,7 @@ const Profile = (route) => {
</h1>
<div className="profileBody">
Last 10 scrobbles...<br/>
<ScrobbleTable data={profile.scrobbles}/>
<ScrobbleTable data={profile.scrobbles}/>
</div>
</div>
);

15
web/src/Pages/Reset.css Normal file
View file

@ -0,0 +1,15 @@
.resetBody {
padding: 20px 5px 5px 5px;
font-size: 16pt;
width: 300px;
}
.resetFields {
width: 100%;
}
.resetButton {
height: 50px;
width: 100%;
margin-top:-5px;
}

153
web/src/Pages/Reset.js Normal file
View file

@ -0,0 +1,153 @@
import React, { useState, useEffect, useContext } from 'react';
import '../App.css';
import './Reset.css';
import { Button } from 'reactstrap';
import { Formik, Form, Field } from 'formik';
import ScaleLoader from 'react-spinners/ScaleLoader';
import { validateResetPassword, sendPasswordReset } from '../Api/index';
import AuthContext from '../Contexts/AuthContext';
const Reset = (route) => {
let boolTrue = true;
const [loading, setLoading] = useState(true);
const [reset, setReset] = useState({});
const [sent, setSent] = useState(false);
let { ResetPassword } = useContext(AuthContext);
let reqToken = false;
if (route && route.match && route.match.params && route.match.params.token) {
reqToken = route.match.params.token
}
const sendReset = (values) => {
sendPasswordReset(values).then(() => {
setSent(true);
});
}
useEffect(() => {
if (!reqToken) {
setLoading(false);
return false;
}
validateResetPassword(reqToken)
.then(data => {
setReset(data);
console.log(data)
setLoading(false);
})
}, [reqToken])
if (loading) {
return (
<div className="pageWrapper">
<ScaleLoader color="#6AD7E5" />
</div>
)
}
if (sent) {
return (
<div className="pageWrapper">
<h1>
Check your email!
</h1>
</div>
)
}
if (!reqToken) {
return (
<div className="pageWrapper">
<h1>
Reset Password
</h1>
<div className="loginBody">
<Formik
initialValues={{ email: '' }}
onSubmit={values => sendReset(values)}
>
<Form>
<label>
Email<br/>
<Field
name="email"
type="email"
required={boolTrue}
className="loginFields"
/>
</label>
<br/><br/>
<Button
color="primary"
type="submit"
className="loginButton"
disabled={loading}
>{loading ? <ScaleLoader color="#FFF" size={35} /> : "Reset"}</Button>
</Form>
</Formik>
</div>
</div>
)
}
if (reqToken && !reset.valid) {
return (
<div className="pageWrapper">
Invalid Reset Token or Token expired
</div>
)
}
return (
<div className="pageWrapper">
<h1>
Reset Password
</h1>
<div className="resetBody">
<Formik
initialValues={{ password: '', comfirmpassword: '', token: reqToken }}
onSubmit={values => ResetPassword(values)}
>
<Form>
<label>
New Password<br/>
<Field
name="password"
type="password"
required={boolTrue}
className="resetFields"
/>
</label>
<br/>
<label>
Confirm New Password<br/>
<Field
name="comfirmpassword"
type="password"
required={boolTrue}
className="resetFields"
/>
</label>
<Field
name="token"
type="hidden"
className="resetFields"
/>
<br/><br/>
<Button
color="primary"
type="submit"
className="loginButton"
disabled={loading}
>{loading ? <ScaleLoader color="#FFF" size={35} /> : "Reset"}</Button>
</Form>
</Formik>
</div>
</div>
);
}
export default Reset;