implement login

main
Cameron Clough 2022-03-24 00:38:30 +00:00
parent ba6ed936a5
commit 298705c9c7
No known key found for this signature in database
GPG Key ID: BFB3B74B026ED43F
12 changed files with 248 additions and 34 deletions

View File

@ -36,6 +36,11 @@
"ignoreTemplateLiterals": true
}],
// disallow else after a return in an if
// https://eslint.org/docs/rules/no-else-return
// retropilot: allow else-if...
"no-else-return": ["error", { "allowElseIf": true }],
// restrict file extensions that may contain JSX
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-filename-extension.md
// retropilot: we don't care about this

106
package-lock.json generated
View File

@ -23,6 +23,7 @@
"axios": "^0.24.0",
"google-map-react": "^2.1.10",
"prop-types": "^15.8.1",
"query-string": "^7.1.1",
"rc-scrollbars": "^1.1.3",
"react": "^17.0.2",
"react-custom-scrollbars": "^4.2.1",
@ -9704,6 +9705,14 @@
"node": ">=8"
}
},
"node_modules/filter-obj": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
"integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
@ -14632,6 +14641,26 @@
"node": ">=4"
}
},
"node_modules/normalize-url/node_modules/query-string": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
"integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
"dependencies": {
"object-assign": "^4.1.0",
"strict-uri-encode": "^1.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/normalize-url/node_modules/strict-uri-encode": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/npm-run-path": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
@ -16865,15 +16894,20 @@
}
},
"node_modules/query-string": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
"integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz",
"integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==",
"dependencies": {
"object-assign": "^4.1.0",
"strict-uri-encode": "^1.0.0"
"decode-uri-component": "^0.2.0",
"filter-obj": "^1.1.0",
"split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0"
},
"engines": {
"node": ">=0.10.0"
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/querystring": {
@ -19016,6 +19050,14 @@
"node": ">= 6"
}
},
"node_modules/split-on-first": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
"engines": {
"node": ">=6"
}
},
"node_modules/split-string": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
@ -19139,11 +19181,11 @@
"integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ=="
},
"node_modules/strict-uri-encode": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
"integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=",
"engines": {
"node": ">=0.10.0"
"node": ">=4"
}
},
"node_modules/string_decoder": {
@ -29520,6 +29562,11 @@
"to-regex-range": "^5.0.1"
}
},
"filter-obj": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
"integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs="
},
"finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
@ -33263,6 +33310,22 @@
"prepend-http": "^1.0.0",
"query-string": "^4.1.0",
"sort-keys": "^1.0.0"
},
"dependencies": {
"query-string": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
"integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
"requires": {
"object-assign": "^4.1.0",
"strict-uri-encode": "^1.0.0"
}
},
"strict-uri-encode": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM="
}
}
},
"npm-run-path": {
@ -35052,12 +35115,14 @@
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
},
"query-string": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
"integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz",
"integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==",
"requires": {
"object-assign": "^4.1.0",
"strict-uri-encode": "^1.0.0"
"decode-uri-component": "^0.2.0",
"filter-obj": "^1.1.0",
"split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0"
}
},
"querystring": {
@ -36751,6 +36816,11 @@
}
}
},
"split-on-first": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="
},
"split-string": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
@ -36857,9 +36927,9 @@
"integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ=="
},
"strict-uri-encode": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM="
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
"integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY="
},
"string_decoder": {
"version": "1.1.1",

View File

@ -26,6 +26,7 @@
"axios": "^0.24.0",
"google-map-react": "^2.1.10",
"prop-types": "^15.8.1",
"query-string": "^7.1.1",
"rc-scrollbars": "^1.1.3",
"react": "^17.0.2",
"react-custom-scrollbars": "^4.2.1",

View File

@ -2,21 +2,22 @@ import React, { useState } from 'react';
import CssBaseline from '@mui/material/CssBaseline';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import Login from './components/views/login';
import UserAdmin from './components/views/useradmin';
import { getSession } from './api/auth';
import Login from './components/views/Login';
import UserAdmin from './components/views/UserAdmin';
import GlobalSnack from './components/widgets/globalSnack';
import DevicesProvider from './context/devices';
import ToastProvider from './context/toast';
import UserProvider from './context/users';
import * as authenticationController from './controllers/authentication';
function App() {
const [isAuthenticated, setAuthenticated] = useState(false);
authenticationController.getSession().then((session) => {
getSession().then((session) => {
const { authenticated } = session.data;
setAuthenticated(authenticated);
});
}).catch(console.error);
const theme = React.useMemo(
() => createTheme({

44
src/api/auth.js 100644
View File

@ -0,0 +1,44 @@
import * as request from './request';
export async function login(email, password) {
const response = await request.postForm('auth/login', {
email,
password,
});
const { success, data } = response;
if (!success) {
throw new Error(`Could not login: ${JSON.stringify(response)}`);
}
const { jwt, user } = data;
console.debug('Logged in as', user);
request.setAccessToken(jwt);
return jwt;
}
export async function getSession() {
const response = await request.get('auth/session');
const { data } = response.data;
return data.user;
}
export async function refreshAccessToken(code, provider) {
const resp = await request.postForm('session', {
code,
provider,
});
const { access_token: accessToken } = resp;
if (accessToken) {
request.setAccessToken(accessToken);
return accessToken;
} else if (resp.response) {
throw new Error(`Could not exchange oauth code for access token: response ${resp.response}`);
} else if (resp.error) {
throw new Error(`Could not exchange oauth code for access token: error ${resp.error}`);
} else {
console.warn('refreshAccessToken: unexpected response', resp);
throw new Error('Could not exchange oauth code for access token');
}
}

View File

@ -0,0 +1,7 @@
import * as request from './request';
export async function listDevices() {
return request.get('devices');
}
export default null;

View File

@ -0,0 +1,56 @@
import qs from 'query-string';
export default class RequestConfig {
constructor(baseUrl = process.env.REACT_APP_API_URL) {
this.baseUrl = `${baseUrl}${!baseUrl.endsWith('/') ? '/' : ''}api/`;
this.defaultHeaders = {
'Content-Type': 'application/json',
};
}
setAccessToken(accessToken) {
if (accessToken) {
this.defaultHeaders.Authorization = `JWT ${accessToken}`;
}
}
async request(method, path, params, dataJson = true, responseJson = true) {
const headers = { ...this.defaultHeaders };
if (!dataJson) {
headers['Content-Type'] = 'application/x-www-form-urlencoded';
}
let url = this.baseUrl + path;
let body;
if (params && Object.keys(params).length > 0) {
if (method === 'GET' || method === 'HEAD') {
url += `?${qs.stringify(params)}`;
} else if (dataJson) {
body = JSON.stringify(params);
} else {
body = qs.stringify(params);
}
}
console.debug(`fetch ${method} ${url}`);
const response = await fetch(url, {
method,
headers,
body,
});
if (!response.ok) {
const error = await response.text();
throw new Error(`${response.status} ${response.statusText}: ${error}`);
} else if (!responseJson) {
return response;
}
return response.json();
}
}
['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD'].forEach((method) => {
const methodName = method.toLowerCase();
RequestConfig.prototype[methodName] = async function (path, params, dataJson, responseJson) {
return this.request(method, path, params, dataJson, responseJson);
};
});

31
src/api/request.js 100644
View File

@ -0,0 +1,31 @@
import RequestConfig from './instance';
const request = new RequestConfig();
export function setAccessToken(accessToken) {
request.setAccessToken(accessToken);
}
export async function get(endpoint, data) {
return request.get(endpoint, data);
}
export async function post(endpoint, data) {
return request.post(endpoint, data);
}
export async function postForm(endpoint, data) {
return request.post(endpoint, data, false);
}
export async function patch(endpoint, data) {
return request.patch(endpoint, data);
}
export async function put(endpoint, data) {
return request.put(endpoint, data);
}
export async function del(endpoint, data) {
return request.delete(endpoint, data);
}

View File

@ -9,22 +9,21 @@ import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import { UserContext } from '../../context/users';
import { login } from '../../api/auth';
export default function SignIn() {
export default function Login() {
const [loading, setLoading] = useState(false);
const [state, dispatch] = useContext(UserContext);
console.log('component', state);
const handleSubmit = (event) => {
const handleSubmit = async (event) => {
dispatch({ type: 'toggle_button' });
event.preventDefault();
const data = new FormData(event.currentTarget);
// eslint-disable-next-line no-console
console.log({
email: data.get('email'),
password: data.get('password'),
});
login(data.get('email'), data.get('password'))
.then((result) => console.log('result', result))
.catch(console.error);
setLoading(true);
};
@ -80,7 +79,7 @@ export default function SignIn() {
Sign In
</LoadingButton>
<Link href="#Forgot" variant="body2">
<Link href="#forgot" variant="body2">
New Here or Forgotten password?
</Link>
</Box>

View File

@ -6,7 +6,7 @@ import React, {
} from 'react';
import PropTypes from 'prop-types';
import * as deviceController from '../../controllers/devices';
import { listDevices } from '../../api/devices';
import ACTIONS from './actions';
import reducer from './reducer';
@ -32,11 +32,11 @@ function DevicesProvider({ children }) {
}
};
deviceController.getAllDevices().then((devices) => {
listDevices().then((devices) => {
console.log('devices store', devices);
dispatch({ type: ACTIONS.FETCH_ALL_DONGLES, data: devices });
});
}).catch(console.error);
return () => {
// Clean up the websocket