finally got authentication working
This commit is contained in:
parent
d3af43bc7c
commit
fa97a8f126
12 changed files with 297 additions and 43 deletions
26
bfrontend/package-lock.json
generated
26
bfrontend/package-lock.json
generated
|
@ -12306,6 +12306,18 @@
|
|||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"react-redux": {
|
||||
"version": "7.2.2",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.2.tgz",
|
||||
"integrity": "sha512-8+CQ1EvIVFkYL/vu6Olo7JFLWop1qRUeb46sGtIMDCSpgwPQq8fPLpirIB0iTqFe9XYEFPHssdX8/UwN6pAkEA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.1",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"react-refresh": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz",
|
||||
|
@ -12538,6 +12550,15 @@
|
|||
"strip-indent": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"redux": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz",
|
||||
"integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"symbol-observable": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"regenerate": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
||||
|
@ -14234,6 +14255,11 @@
|
|||
"util.promisify": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"symbol-observable": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
|
||||
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
|
||||
},
|
||||
"symbol-tree": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||
|
|
|
@ -8,8 +8,10 @@
|
|||
"@testing-library/user-event": "^12.6.0",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "4.0.1",
|
||||
"redux": "^4.0.5",
|
||||
"web-vitals": "^0.2.4"
|
||||
},
|
||||
"scripts": {
|
||||
|
@ -18,6 +20,7 @@
|
|||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"proxy": "http://localhost:3002",
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
|
|
|
@ -33,9 +33,9 @@ APIRequest.authenticated = async function(endpoint, options) {
|
|||
if (!options) options = {};
|
||||
if (!options.headers) options.headers = {};
|
||||
|
||||
options.headers = {
|
||||
options = {
|
||||
credentials: 'include',
|
||||
...options.headers
|
||||
...options
|
||||
};
|
||||
|
||||
try {
|
||||
|
|
|
@ -1,30 +1,13 @@
|
|||
import APIRequest from './APIRequest';
|
||||
|
||||
class Authenticator {
|
||||
constructor(method='cookie') {
|
||||
this.method = method;
|
||||
this.__token = undefined;
|
||||
this.user = undefined;
|
||||
const Authenticator = {
|
||||
getLoggedInUserFromCookie: async function() {
|
||||
const { json, isOK, err } = await APIRequest.authenticated('/api/v1/users/current/info');
|
||||
if (!isOK && err) throw new Error(err);
|
||||
if (!isOK && !err) return undefined;
|
||||
if (!json || !json.user) return undefined;
|
||||
return json.user;
|
||||
}
|
||||
}
|
||||
|
||||
Authenticator.prototype.fetchLoggedInUserInfo = async function(token) {
|
||||
const { json, isOK } = await APIRequest.authenticated();
|
||||
if (!isOK) return false;
|
||||
if (!json.user) return false;
|
||||
|
||||
this.user = json.user;
|
||||
this.__token = json.user.token;
|
||||
return true;
|
||||
}
|
||||
|
||||
Authenticator.prototype.setToken = function(token) {
|
||||
this.__token = token;
|
||||
};
|
||||
|
||||
Authenticator.prototype.getToken = function(token) {
|
||||
return this.__token;
|
||||
};
|
||||
|
||||
const authenticator = new Authenticator('cookie');
|
||||
export default authenticator;
|
||||
export default Authenticator;
|
|
@ -1,23 +1,39 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import authenticator from './../Authenticator';
|
||||
import Login from './Auth/Login';
|
||||
import Root from './Root';
|
||||
import Authenticator from './../Authenticator';
|
||||
|
||||
export default function App() {
|
||||
const [isLoggedIn, setIsLoggedIn] = useState([]);
|
||||
import { useEffect } from 'react';
|
||||
import { useDispatch, connect } from 'react-redux'
|
||||
import { BrowserRouter, Switch, Route } from 'react-router-dom';
|
||||
|
||||
function App({ user }) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
authenticator.fetchLoggedInUserInfo()
|
||||
Authenticator.getLoggedInUserFromCookie()
|
||||
.then((res) => {
|
||||
setIsLoggedIn(res);
|
||||
dispatch({ type: 'authenticator/updatelocaluserobject', user: res })
|
||||
});
|
||||
}, [authenticator.user]);
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<BrowserRouter>
|
||||
<Switch>
|
||||
<Route path="/login">
|
||||
|
||||
<Login />
|
||||
</Route>
|
||||
<Route path="/">
|
||||
<Root user={user} />
|
||||
</Route>
|
||||
</Switch>
|
||||
</Router>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
|
||||
const stateToProps = (state) => {
|
||||
return {
|
||||
user: state?.user || undefined
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(stateToProps)(App);
|
|
@ -1,3 +1,63 @@
|
|||
export default function Login() {
|
||||
import { useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import Notification from '../Notification';
|
||||
import APIRequest from '../../APIRequest';
|
||||
import Authenticator from './../../Authenticator';
|
||||
import { getLoginMessageFromError } from '../../Errors'
|
||||
|
||||
export default function Login() {
|
||||
const history = useHistory();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [ usernameInput, setUsernameInput ] = useState();
|
||||
const [ passwordInput, setPasswordInput ] = useState();
|
||||
|
||||
const [ info, setInfo ] = useState();
|
||||
|
||||
const handleLoginContinueButton = async () => {
|
||||
if (!usernameInput || !passwordInput) {
|
||||
setInfo('One of more fields is not filled in.');
|
||||
return;
|
||||
}
|
||||
|
||||
const { json, isOK } = await APIRequest('/api/v1/users/token/create', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: usernameInput,
|
||||
password: passwordInput,
|
||||
alsoSetCookie: true
|
||||
})
|
||||
});
|
||||
|
||||
if (!isOK && json) {
|
||||
setInfo(getLoginMessageFromError(json));
|
||||
return;
|
||||
}
|
||||
if (!isOK) {
|
||||
setInfo('Something went wrong');
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await Authenticator.getLoggedInUserFromCookie();
|
||||
dispatch({ type: 'authenticator/updatelocaluserobject', user: res });
|
||||
|
||||
history.push('/');
|
||||
}
|
||||
|
||||
return (
|
||||
<div id="login-container">
|
||||
<input type="text" name="username" onChange={ ({ target }) => setUsernameInput(target.value) } />
|
||||
<br />
|
||||
<input type="password" name="password" onChange={ ({ target }) => setPasswordInput(target.value) } />
|
||||
<br />
|
||||
<button id="login-submit" onClick={ handleLoginContinueButton }>Continue</button>
|
||||
<Notification text={ info } />
|
||||
</div>
|
||||
);
|
||||
}
|
7
bfrontend/src/Components/Notification.js
Normal file
7
bfrontend/src/Components/Notification.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
export default function Notification({ text }) {
|
||||
return (
|
||||
<div className="notification">
|
||||
<p>{ text }</p>
|
||||
</div>
|
||||
);
|
||||
}
|
21
bfrontend/src/Components/Root.js
Normal file
21
bfrontend/src/Components/Root.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
export default function Root(props) {
|
||||
const history = useHistory();
|
||||
|
||||
if (props.user) {
|
||||
return (
|
||||
<div id="root-container">
|
||||
<h1>Welcome, { props.user.username }</h1>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div id="root-container">
|
||||
<h1>Welcome, - nevermind, you aren't logged in</h1>
|
||||
<button onClick={ () => { history.push('/login') } }>Log in</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
const config = {
|
||||
apiUrl: 'http://localhost:3002'
|
||||
apiUrl: 'http://localhost:3000'
|
||||
};
|
||||
|
||||
export default config;
|
113
bfrontend/src/Errors.js
Normal file
113
bfrontend/src/Errors.js
Normal file
|
@ -0,0 +1,113 @@
|
|||
const getLoginMessageFromError = (json) => {
|
||||
switch (json.message) {
|
||||
case 'ERROR_REQUEST_LOGIN_INVALID': {
|
||||
return 'Invalid username or password.';
|
||||
}
|
||||
|
||||
case 'ERROR_ACCESS_DENIED': {
|
||||
return 'You are not allowed to perform this action.'
|
||||
}
|
||||
|
||||
default: {
|
||||
return 'Unknown error. Something went wrong.'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getSignupMessageFromError = (json) => {
|
||||
switch (json.message) {
|
||||
case 'ERROR_REQUEST_INVALID_DATA': {
|
||||
|
||||
switch (json.errors[0].param) {
|
||||
case 'username': {
|
||||
return 'Invalid username. Username must be between 3 and 32 characters long, and be alphanumeric.';
|
||||
}
|
||||
case 'password': {
|
||||
return 'Invalid password. Password must be at least 8 characters long and at most 128 characters.';
|
||||
}
|
||||
case 'email': {
|
||||
return 'Invalid email.';
|
||||
}
|
||||
case 'specialCode': {
|
||||
return 'Invalid special code.';
|
||||
}
|
||||
|
||||
default: {
|
||||
return 'Invalid value sent to server. Something went wrong.';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case 'ERROR_ACCESS_DENIED': {
|
||||
return 'You are not allowed to perform this action.'
|
||||
}
|
||||
|
||||
case 'ERROR_REQUEST_USERNAME_EXISTS': {
|
||||
return 'That username is taken.';
|
||||
}
|
||||
|
||||
default: {
|
||||
return 'Unknown error. Something went wrong.'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getCreatePostError = (json) => {
|
||||
switch (json.message) {
|
||||
case 'ERROR_REQUEST_INVALID_DATA': {
|
||||
switch (json.errors[0].param) {
|
||||
case 'title': {
|
||||
return 'Invalid title. Must be between 3 and 32 characters.';
|
||||
}
|
||||
case 'body': {
|
||||
return 'Invalid content. Must be between 3 and 1000 characters';
|
||||
}
|
||||
case 'category': {
|
||||
return 'Invalid category. Something went wrong.';
|
||||
}
|
||||
default: {
|
||||
return 'Invalid value sent to server. Something went wrong.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case 'ERROR_CATEGORY_NOT_FOUND': {
|
||||
return 'The category you tried to post to no longer exists.';
|
||||
}
|
||||
|
||||
case 'ERROR_ACCESS_DENIED': {
|
||||
return 'You are not allowed to perform this action.'
|
||||
}
|
||||
|
||||
default: {
|
||||
return 'Unknown error. Something went wrong.';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getCreateCategoryError = (json) => {
|
||||
switch (json.message) {
|
||||
case 'ERROR_REQUEST_INVALID_DATA': {
|
||||
switch (json.errors[0].param) {
|
||||
case 'title': {
|
||||
return 'Invalid title. Title must be between 3 and 32 characters.';
|
||||
}
|
||||
|
||||
default: {
|
||||
return 'Invalid value sent to server. Something went wrong.'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case 'ERROR_ACCESS_DENIED': {
|
||||
return 'You are not allowed to perform this action.'
|
||||
}
|
||||
|
||||
default: {
|
||||
return 'Unknown error. Something went wrong.';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { getLoginMessageFromError, getSignupMessageFromError, getCreatePostError, getCreateCategoryError }
|
|
@ -1,11 +1,16 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import store from './store';
|
||||
import './index.css';
|
||||
import App from './Components/App';
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
<Provider store={ store }>
|
||||
<App />
|
||||
</Provider>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
20
bfrontend/src/store.js
Normal file
20
bfrontend/src/store.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { createStore } from 'redux';
|
||||
|
||||
const reducer = (state, payload) => {
|
||||
switch (payload.type) {
|
||||
case 'authenticator/updatelocaluserobject': {
|
||||
return {
|
||||
...state,
|
||||
user: payload.user
|
||||
}
|
||||
}
|
||||
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
|
||||
|
||||
export default store;
|
Loading…
Reference in a new issue