- Clean up login/redirect flow
- Add redirect when not authed on other endpoints
- Add GET /stats endpoint for overal stats
This commit is contained in:
Daniel Mason 2021-03-30 15:02:04 +13:00
parent 5fd9d41069
commit 038823055a
Signed by: idanoo
GPG Key ID: 387387CDBC02F132
23 changed files with 413 additions and 178 deletions

View File

@ -3,7 +3,7 @@ stages:
- bundle - bundle
variables: variables:
VERSION: 0.0.2 VERSION: 0.0.3
build-go: build-go:
image: golang:1.16.2 image: golang:1.16.2

View File

@ -22,7 +22,7 @@ Copy .env.example to .env and set variables. You can use https://www.grc.com/pas
cp .env.example .env # Fill in the blanks cp .env.example .env # Fill in the blanks
go mod tidy go mod tidy
CGO_ENABLED=0 go run cmd/go-scrobble/*.go CGO_ENABLED=0 go run cmd/go-scrobble/*.go
# In another terminal set web/.env.development # In another terminal cp web/.env.example web/.env.development and set vars
cd web && npm install && npm start --env development cd web && npm install && npm start --env development
@ -32,6 +32,7 @@ Access dev frontend @ http://127.0.0.1:3000 + API @ http://127.0.0.1:42069/api/v
We need to build NPM package, and then ship web/build with the binary. We need to build NPM package, and then ship web/build with the binary.
cp .env.example .env # Fill in the blanks cp .env.example .env # Fill in the blanks
cp web/.env.example web/.env.production
cd web npm install --production && npm run build --env production cd web npm install --production && npm run build --env production
go build -o goscrobble cmd/go-scrobble/*.go go build -o goscrobble cmd/go-scrobble/*.go
./goscrobble ./goscrobble

View File

@ -1,3 +1,11 @@
# 0.0.4
- Display stats on homepage
# 0.0.3
- Clean up login/redirect flow
- Add redirect when not authed on other endpoints
- Add GET /stats endpoint for overal stats
# 0.0.2 # 0.0.2
- Login flow working.. - Login flow working..
- Jellyfin scrobble working - Jellyfin scrobble working

View File

@ -54,6 +54,7 @@ func HandleRequests(port string) {
// No Auth // No Auth
v1.HandleFunc("/register", limitMiddleware(handleRegister, heavyLimiter)).Methods("POST") v1.HandleFunc("/register", limitMiddleware(handleRegister, heavyLimiter)).Methods("POST")
v1.HandleFunc("/login", limitMiddleware(handleLogin, standardLimiter)).Methods("POST") v1.HandleFunc("/login", limitMiddleware(handleLogin, standardLimiter)).Methods("POST")
v1.HandleFunc("/stats", handleStats).Methods("GET")
// This just prevents it serving frontend stuff over /api // This just prevents it serving frontend stuff over /api
r.PathPrefix("/api") r.PathPrefix("/api")
@ -233,6 +234,19 @@ func handleLogin(w http.ResponseWriter, r *http.Request) {
w.Write(data) w.Write(data)
} }
// handleStats - Returns stats for homepage
func handleStats(w http.ResponseWriter, r *http.Request) {
stats, err := getStats()
if err != nil {
throwOkError(w, err.Error())
return
}
js, _ := json.Marshal(&stats)
w.WriteHeader(http.StatusOK)
w.Write(js)
}
// serveEndpoint - API stuffs // serveEndpoint - API stuffs
func handleIngress(w http.ResponseWriter, r *http.Request, userUuid string) { func handleIngress(w http.ResponseWriter, r *http.Request, userUuid string) {
bodyJson, err := decodeJson(r.Body) bodyJson, err := decodeJson(r.Body)

View File

@ -0,0 +1,94 @@
package goscrobble
import (
"encoding/json"
"errors"
"log"
"time"
)
type StatsRequest struct {
Users int `json:"users"`
Scrobbles int `json:"scrobbles"`
Tracks int `json:"songs"`
Artists int `json:"artists"`
LastUpdated time.Time `json:"last_updated"`
}
func getStats() (StatsRequest, error) {
js := getRedisVal("stats")
statsReq := StatsRequest{}
var err error
if js != "" {
// If cached, deserialize and return
err = json.Unmarshal([]byte(js), &statsReq)
if err != nil {
log.Printf("Error unmarshalling stats json: %+v", err)
return statsReq, errors.New("Error fetching stats")
}
// Check if older than 5 min - we want to update async for the next caller
now := time.Now()
if now.Sub(statsReq.LastUpdated) > time.Duration(5)*time.Minute {
go goFetchStats()
}
} else {
// If not cached, pull data then return
statsReq, err = fetchStats()
if err != nil {
log.Printf("Error fetching stats: %+v", err)
return statsReq, errors.New("Error fetching stats")
}
}
return statsReq, nil
}
// goFetchStats - Async call
func goFetchStats() {
_, _ = fetchStats()
}
func fetchStats() (StatsRequest, error) {
statsReq := StatsRequest{}
var err error
statsReq.Users, err = getDbCount("SELECT COUNT(*) FROM `users` WHERE `active` = 1")
if err != nil {
log.Printf("Failed to fetch user count: %+v", err)
return statsReq, errors.New("Failed to fetch stats")
}
statsReq.Scrobbles, err = getDbCount("SELECT COUNT(*) FROM `scrobbles`")
if err != nil {
log.Printf("Failed to fetch scrobble count: %+v", err)
return statsReq, errors.New("Failed to fetch stats")
}
statsReq.Tracks, err = getDbCount("SELECT COUNT(*) FROM `tracks`")
if err != nil {
log.Printf("Failed to fetch track count: %+v", err)
return statsReq, errors.New("Failed to fetch stats")
}
statsReq.Artists, err = getDbCount("SELECT COUNT(*) FROM `artists`")
if err != nil {
log.Printf("Failed to fetch artist count: %+v", err)
return statsReq, errors.New("Failed to fetch stats")
}
// Mark the time this was last updated
statsReq.LastUpdated = time.Now()
b, err := json.Marshal(statsReq)
if err != nil {
return statsReq, errors.New("Failed to fetch stats")
}
err = setRedisVal("stats", string(b))
if err != nil {
return statsReq, errors.New("Failed to fetch stats")
}
return statsReq, nil
}

2
web/.env.example Normal file
View File

@ -0,0 +1,2 @@
REACT_APP_API_URL=http://127.0.0.1:42069/api/v1/
REACT_APP_REGISTRATION_DISABLED=false

29
web/src/Actions/api.js Normal file
View File

@ -0,0 +1,29 @@
import { toast } from 'react-toastify';
import ApiService from "../Services/api.service";
export const getStats = () => () => {
return ApiService.getStats().then(
(data) => {
console.log(data);
if (data.error) {
toast.error(data.error)
return Promise.reject();
}
return Promise.resolve();
},
(error) => {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
console.log(message);
toast.error(message);
return Promise.reject();
}
);
};

View File

@ -3,20 +3,30 @@ import {
REGISTER_FAIL, REGISTER_FAIL,
LOGIN_SUCCESS, LOGIN_SUCCESS,
LOGIN_FAIL, LOGIN_FAIL,
SET_MESSAGE,
} from "./types"; } from "./types";
import { toast } from 'react-toastify'
import { toast } from 'react-toastify';
import jwt from 'jwt-decode'
import AuthService from "../Services/auth.service"; import AuthService from "../Services/auth.service";
export const register = (username, email, password) => (dispatch) => { export const register = (username, email, password) => (dispatch) => {
return AuthService.register(username, email, password).then( return AuthService.register(username, email, password).then(
(response) => { (data) => {
if (data.message) {
toast.success('Successfully registered. Please sign in');
dispatch({ dispatch({
type: REGISTER_SUCCESS, type: REGISTER_SUCCESS,
}); });
return Promise.resolve(); return Promise.resolve();
}
toast.error(data.error ? data.error: 'An Unknown Error has occurred')
dispatch({
type: REGISTER_FAIL,
});
return Promise.reject();
}, },
(error) => { (error) => {
const message = const message =
@ -26,13 +36,10 @@ import {
error.message || error.message ||
error.toString(); error.toString();
dispatch({ toast.error(message);
type: REGISTER_FAIL,
});
dispatch({ dispatch({
type: SET_MESSAGE, type: REGISTER_FAIL,
payload: message,
}); });
return Promise.reject(); return Promise.reject();
@ -45,9 +52,11 @@ import {
(data) => { (data) => {
if (data.token) { if (data.token) {
toast.success('Login Success'); toast.success('Login Success');
let user = jwt(data.token)
dispatch({ dispatch({
type: LOGIN_SUCCESS, type: LOGIN_SUCCESS,
payload: { user: data }, payload: { jwt: data.token, sub: user.sub, exp: user.exp },
}); });
return Promise.resolve(); return Promise.resolve();
} }
@ -71,17 +80,17 @@ import {
type: LOGIN_FAIL, type: LOGIN_FAIL,
}); });
// dispatch({
// type: SET_MESSAGE,
// payload: message,
// });
return Promise.reject(); return Promise.reject();
} }
); );
}; };
export const logout = () => () => { export const logout = () => (dispatch) => {
AuthService.logout(); AuthService.logout();
// dispatch({
// type: LOGOUT,
// });
window.location.reload(); window.location.reload();
}; };

View File

@ -0,0 +1,13 @@
const eventBus = {
on(event, callback) {
document.addEventListener(event, (e) => callback(e.detail));
},
dispatch(event, data) {
document.dispatchEvent(new CustomEvent(event, { detail: data }));
},
remove(event, callback) {
document.removeEventListener(event, callback);
},
};
export default eventBus

View File

@ -11,8 +11,6 @@ import Register from './Pages/Register';
import Navigation from './Components/Navigation'; import Navigation from './Components/Navigation';
import { logout } from './Actions/auth'; import { logout } from './Actions/auth';
import { clearMessage } from './Actions/message';
import { history } from './Helpers/history';
import { Route, Switch, withRouter } from 'react-router-dom'; import { Route, Switch, withRouter } from 'react-router-dom';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Component } from 'react'; import { Component } from 'react';
@ -37,10 +35,6 @@ class App extends Component {
// exact="true".. it has to be a bool :| // exact="true".. it has to be a bool :|
true: true, true: true,
}; };
history.listen((location) => {
props.dispatch(clearMessage()); // clear message when changing location
});
} }
componentDidMount() { componentDidMount() {

View File

View File

@ -0,0 +1,44 @@
import React from 'react';
import '../App.css';
import './HomeBanner.css';
import { getStats } from '../Actions/api';
class HomeBanner extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
userCount: 0,
scrobbleCount: 0,
trackCount: 0,
artistCount: 0,
};
}
componentDidMount() {
getStats()
// .then((data) => {
// this.setState({
// loading: false,
// userCount: data.users,
// scrobbleCount: data.scrobbles,
// trackCount: data.tracks,
// artistCount: data.artists,
// });
// })
// .catch(() => {
// this.setState({
// loading: false
// });
// });
}
render() {
return (
<div className="container">
</div>
);
}
}
export default HomeBanner;

View File

@ -5,6 +5,12 @@ import logo from '../logo.png';
import './Navigation.css'; import './Navigation.css';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { logout } from '../Actions/auth'; import { logout } from '../Actions/auth';
import eventBus from "../Actions/eventBus";
import {
LOGIN_SUCCESS,
LOGOUT,
} from "../Actions/types";
const menuItems = [ const menuItems = [
'Home', 'Home',
@ -24,32 +30,62 @@ class Navigation extends Component {
} }
componentDidMount() { componentDidMount() {
const isLoggedIn = this.props.isLoggedIn; const { isLoggedIn } = this.props;
if (isLoggedIn) { if (isLoggedIn) {
this.setState({ this.setState({
isLoggedIn: true, isLoggedIn: true,
}); });
} }
eventBus.on(LOGIN_SUCCESS, () =>
this.setState({ isLoggedIn: true })
);
eventBus.on(LOGOUT, () =>
this.setState({ isLoggedIn: false })
);
} }
componentWillUnmount() {
eventBus.remove(LOGIN_SUCCESS);
eventBus.remove(LOGOUT);
}
_handleClick(menuItem) { _handleClick(menuItem) {
this.setState({ active: menuItem }); this.setState({ active: menuItem });
} }
render() { render() {
const activeStyle = { color: '#FFFFFF' }; const activeStyle = { color: '#FFFFFF' };
const renderAuthButtons = () => { const renderAuthButtons = () => {
if (this.state.isLoggedIn) { if (this.state.isLoggedIn) {
return <div className="navLinkLogin"> return <div className="navLinkLogin">
<Link to="/profile" className="navLink">Profile</Link> <Link
to="/profile"
style={this.state.active === "profile" ? activeStyle : {}}
onClick={this._handleClick.bind(this, "profile")}
className="navLink"
>Profile</Link>
<Link to="/" className="navLink" onClick={logout()}>Logout</Link> <Link to="/" className="navLink" onClick={logout()}>Logout</Link>
</div>; </div>;
} else { } else {
return <div className="navLinkLogin"> return <div className="navLinkLogin">
<Link to="/login" className="navLink">Login</Link> <Link
<Link to="/register" className="navLink" history={this.props.history}>Register</Link> to="/login"
style={this.state.active === "login" ? activeStyle : {}}
onClick={this._handleClick.bind(this, "login")}
className="navLink"
>Login</Link>
<Link
to="/register"
className="navLink"
style={this.state.active === "register" ? activeStyle : {}}
onClick={this._handleClick.bind(this, "register")}
history={this.props.history}
>Register</Link>
</div>; </div>;
} }
} }
@ -89,7 +125,7 @@ class Navigation extends Component {
return ( return (
<div> <div>
<Navbar color="dark" dark fixed="top"> <Navbar color="dark" dark fixed="top">
<NavbarBrand href="/" className="mr-auto"><img src={logo} className="nav-logo" alt="logo" /> GoScrobble</NavbarBrand> <NavbarBrand className="mr-auto"><img src={logo} className="nav-logo" alt="logo" /> GoScrobble</NavbarBrand>
{renderMenuButtons()} {renderMenuButtons()}
{renderAuthButtons()} {renderAuthButtons()}
</Navbar> </Navbar>

View File

@ -10,7 +10,6 @@ class Dashboard extends React.Component {
if (!isLoggedIn) { if (!isLoggedIn) {
history.push("/login") history.push("/login")
window.location.reload()
} }
} }
@ -18,7 +17,7 @@ class Dashboard extends React.Component {
return ( return (
<div className="pageWrapper"> <div className="pageWrapper">
<h1> <h1>
Hai Dashboard! Dashboard!
</h1> </h1>
</div> </div>
); );

View File

@ -1,7 +1,10 @@
import logo from '../logo.png'; import logo from '../logo.png';
import '../App.css'; import '../App.css';
import HomeBanner from '../Components/HomeBanner';
import React from 'react';
function Home() { class Home extends React.Component {
render() {
return ( return (
<div className="App-header"> <div className="App-header">
<img src={logo} className="App-logo" alt="logo" /> <img src={logo} className="App-logo" alt="logo" />
@ -16,8 +19,10 @@ function Home() {
> >
gitlab.com/idanoo/go-scrobble gitlab.com/idanoo/go-scrobble
</a> </a>
<HomeBanner />
</div> </div>
); );
} }
}
export default Home; export default Home;

View File

@ -6,6 +6,8 @@ import { Formik, Form, Field } from 'formik';
import ScaleLoader from 'react-spinners/ScaleLoader'; import ScaleLoader from 'react-spinners/ScaleLoader';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { login } from '../Actions/auth'; import { login } from '../Actions/auth';
import eventBus from "../Actions/eventBus";
import { LOGIN_SUCCESS } from '../Actions/types';
class Login extends React.Component { class Login extends React.Component {
constructor(props) { constructor(props) {
@ -13,6 +15,14 @@ class Login extends React.Component {
this.state = {username: '', password: '', loading: false}; this.state = {username: '', password: '', loading: false};
} }
componentDidMount() {
const { history, isLoggedIn } = this.props;
if (isLoggedIn) {
history.push("/dashboard")
}
}
handleLogin(values) { handleLogin(values) {
this.setState({loading: true}); this.setState({loading: true});
@ -22,9 +32,11 @@ class Login extends React.Component {
.then(() => { .then(() => {
this.setState({ this.setState({
loading: false, loading: false,
isLoggedIn: true
}); });
eventBus.dispatch(LOGIN_SUCCESS, { isLoggedIn: true });
history.push("/dashboard"); history.push("/dashboard");
window.location.reload();
}) })
.catch(() => { .catch(() => {
this.setState({ this.setState({
@ -82,10 +94,8 @@ class Login extends React.Component {
function mapStateToProps(state) { function mapStateToProps(state) {
const { isLoggedIn } = state.auth; const { isLoggedIn } = state.auth;
const { message } = state.message;
return { return {
isLoggedIn, isLoggedIn
message
}; };
} }

View File

@ -5,12 +5,10 @@ import { connect } from 'react-redux';
class Profile extends React.Component { class Profile extends React.Component {
componentDidMount() { componentDidMount() {
const { history } = this.props; const { history, isLoggedIn } = this.props;
const isLoggedIn = this.props.isLoggedIn;
if (!isLoggedIn) { if (!isLoggedIn) {
history.push("/login") history.push("/login")
window.location.reload()
} }
} }

View File

@ -1,86 +1,42 @@
import React from 'react'; import React from 'react';
import '../App.css'; import '../App.css';
import './Login.css'; import './Login.css';
import { Button } from 'reactstrap'; import { Button, Form } from 'reactstrap';
import ScaleLoader from "react-spinners/ScaleLoader"; import ScaleLoader from "react-spinners/ScaleLoader";
import { withRouter } from 'react-router-dom' import { register } from '../Actions/auth';
import { Formik, Field } from 'formik';
import { connect } from 'react-redux';
class Register extends React.Component { class Register extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {username: '', email: '', password: '', passwordconfirm: '', loading: false}; this.state = {username: '', email: '', password: '', passwordconfirm: '', loading: false};
this.handleUsernameChange = this.handleUsernameChange.bind(this);
this.handleEmailChange = this.handleEmailChange.bind(this);
this.handlePasswordChange = this.handlePasswordChange.bind(this);
this.handlePasswordConfirmChange = this.handlePasswordConfirmChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
} }
handleUsernameChange(event) { componentDidMount() {
this.setState({username: event.target.value}); const { history, isLoggedIn } = this.props;
if (isLoggedIn) {
history.push("/dashboard")
}
} }
handleEmailChange(event) { handleRegister(values) {
this.setState({email: event.target.value});
}
handlePasswordChange(event) {
this.setState({password: event.target.value});
}
handlePasswordConfirmChange(event) {
this.setState({passwordconfirm: event.target.value});
}
handleSubmit(event) {
event.preventDefault();
if (this.state.password !== this.state.passwordconfirm) {
this.props.addToast('Passwords do not match', { appearance: 'error' });
return
}
// if (this.state.password.len < 8) {
// this.props.addToast('Password must be at least 8 characters', { appearance: 'error' });
// return
// }
this.setState({loading: true}); this.setState({loading: true});
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
timeout: 5000,
body: JSON.stringify({
username: this.state.username,
email: this.state.email,
password: this.state.password,
})
};
const apiUrl = process.env.REACT_APP_API_URL + '/api/v1/register'; const { dispatch, history } = this.props;
console.log(apiUrl);
fetch(apiUrl, requestOptions) dispatch(register(values.username, values.email, values.password))
.then((response) => { .then(() => {
if (response.status === 429) { this.setState({
this.props.addToast("Rate limited. Please try again soon", { appearance: 'error' }); loading: false,
return "{}" });
} else { history.push("/login");
return response.json()
}
}) })
.then((function(data) {
console.log(data);
if (data.error) {
this.props.addToast(data.error, { appearance: 'error' });
} else if (data.message) {
this.props.addToast(data.message, { appearance: 'success' });
this.props.history.push('/login')
}
this.setState({loading: false});
}).bind(this))
.catch(() => { .catch(() => {
this.props.addToast('Error submitting form. Please try again', { appearance: 'error' }); this.setState({
this.setState({loading: false}); loading: false
});
}); });
} }
@ -98,47 +54,46 @@ class Register extends React.Component {
Register Register
</h1> </h1>
<div className="loginBody"> <div className="loginBody">
<form onSubmit={this.handleSubmit}> <Formik
initialValues={{ username: '', email: '', password: '', passwordconfirm: '' }}
onSubmit={async values => this.handleRegister(values)}>
<Form>
<label> <label>
Username*<br/> Username*<br/>
<input <Field
name="username"
type="text" type="text"
required={trueBool} required={trueBool}
className="loginFields" className="loginFields"
value={this.state.username}
onChange={this.handleUsernameChange}
/> />
</label> </label>
<br/> <br/>
<label> <label>
Email<br/> Email<br/>
<input <Field
name="email"
type="email" type="email"
className="loginFields" className="loginFields"
value={this.state.email}
onChange={this.handleEmailChange}
/> />
</label> </label>
<br/> <br/>
<label> <label>
Password*<br/> Password*<br/>
<input <Field
name="password"
type="password" type="password"
required={trueBool} required={trueBool}
className="loginFields" className="loginFields"
value={this.state.password}
onChange={this.handlePasswordChange}
/> />
</label> </label>
<br/> <br/>
<label> <label>
Password*<br/> Confirm Password*<br/>
<input <Field
name="passwordconfirm"
type="password" type="password"
required={trueBool} required={trueBool}
className="loginFields" className="loginFields"
value={this.state.passwordconfirm}
onChange={this.handlePasswordConfirmChange}
/> />
</label> </label>
<br/><br/> <br/><br/>
@ -148,7 +103,8 @@ class Register extends React.Component {
className="loginButton" className="loginButton"
disabled={this.state.loading} disabled={this.state.loading}
>{this.state.loading ? <ScaleLoader color="#FFF" size={35} /> : "Register"}</Button> >{this.state.loading ? <ScaleLoader color="#FFF" size={35} /> : "Register"}</Button>
</form> </Form>
</Formik>
</div> </div>
</div> </div>
} }
@ -157,4 +113,11 @@ class Register extends React.Component {
} }
} }
export default withRouter(Register); function mapStateToProps(state) {
const { isLoggedIn } = state.auth;
return {
isLoggedIn
};
}
export default connect(mapStateToProps)(Register);

View File

@ -6,11 +6,11 @@ import {
LOGOUT, LOGOUT,
} from "../Actions/types"; } from "../Actions/types";
const jwt = localStorage.getItem("jwt"); const user = JSON.parse(localStorage.getItem('user'));
const initialState = jwt const initialState = user
? { isLoggedIn: true, jwt } ? { isLoggedIn: true, user: user }
: { isLoggedIn: false, jwt }; : { isLoggedIn: false, user: null };
export default function authReducer(state = initialState, action) { export default function authReducer(state = initialState, action) {
const { type, payload } = action; const { type, payload } = action;
@ -30,13 +30,16 @@ import {
return { return {
...state, ...state,
isLoggedIn: true, isLoggedIn: true,
user: payload.user, user: {
jwt: payload.jwt,
uuid: payload.sub,
exp: payload.exp,
}
}; };
case LOGIN_FAIL: case LOGIN_FAIL:
return { return {
...state, ...state,
isLoggedIn: false, isLoggedIn: false,
user: null,
}; };
case LOGOUT: case LOGOUT:
return { return {

View File

@ -0,0 +1,13 @@
import axios from "axios";
class ApiService {
async getStats() {
return axios.get(process.env.REACT_APP_API_URL + "stats")
.then((response) => {
return response.data;
}
);
}
}
export default new ApiService();

View File

@ -1,8 +1,8 @@
export default function authHeader() { export default function authHeader() {
const token = JSON.parse(localStorage.getItem('jwt')); const auth = localStorage.getItem('user');
if (token) { if (auth && auth.jwt) {
return { Authorization: 'Bearer ' + token }; return { Authorization: 'Bearer ' + auth.jwt };
} else { } else {
return {}; return {};
} }

View File

@ -7,10 +7,13 @@ class AuthService {
.post(process.env.REACT_APP_API_URL + "login", { username, password }) .post(process.env.REACT_APP_API_URL + "login", { username, password })
.then((response) => { .then((response) => {
if (response.data.token) { if (response.data.token) {
let user = jwt(response.data.token) let expandedUser = jwt(response.data.token)
localStorage.setItem("jwt", response.data.token); let user = {
localStorage.setItem("uuid", user.sub); jwt: response.data.token,
localStorage.setItem("exp", user.exp); uuid: expandedUser.sub,
exp: expandedUser.exp,
}
localStorage.setItem('user', JSON.stringify(user))
} }
return response.data; return response.data;
@ -18,9 +21,7 @@ class AuthService {
} }
logout() { logout() {
localStorage.removeItem("jwt"); localStorage.removeItem("user");
localStorage.removeItem("uuid");
localStorage.removeItem("exp");
} }
register(username, email, password) { register(username, email, password) {

View File

@ -6,7 +6,6 @@ import { HashRouter } from 'react-router-dom';
import { ToastContainer } from 'react-toastify'; import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.min.css' import 'react-toastify/dist/ReactToastify.min.css'
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import store from "./store"; import store from "./store";
ReactDOM.render( ReactDOM.render(