aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoreug-vs <eug-vs@keemail.me>2020-11-15 04:07:33 +0300
committereug-vs <eug-vs@keemail.me>2020-11-15 04:07:33 +0300
commit81b46581ab79f8f5e9e132e00e5d1b8e9182dd46 (patch)
tree7c46c3fd9b2b66af20ce91b61ac889d327c1e492
parenteb2e929e2188ccfd9f575a5aa425024bf1fec67f (diff)
downloadfamcs-kit-81b46581ab79f8f5e9e132e00e5d1b8e9182dd46.tar.gz
feat: implement authentication
-rw-r--r--src/containers/LoginSection/LoginSection.tsx22
-rw-r--r--src/hooks/APIClient.ts8
-rw-r--r--src/hooks/useAuth.tsx70
-rw-r--r--src/hooks/useLocalStorage.ts20
-rw-r--r--src/index.tsx19
5 files changed, 126 insertions, 13 deletions
diff --git a/src/containers/LoginSection/LoginSection.tsx b/src/containers/LoginSection/LoginSection.tsx
index b870c7d..812afd8 100644
--- a/src/containers/LoginSection/LoginSection.tsx
+++ b/src/containers/LoginSection/LoginSection.tsx
@@ -1,7 +1,9 @@
-import React from 'react';
+import React, { useState } from 'react';
import { ContentSection } from 'react-benzin';
import { Link, TextField, Button } from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
+import { useAuth } from '../../hooks/useAuth';
+
const useStyles = makeStyles(theme => ({
form: {
@@ -17,21 +19,37 @@ const useStyles = makeStyles(theme => ({
const LoginSection: React.FC = () => {
const classes = useStyles();
+ const { login, isAuthenticated } = useAuth();
+ const [username, setUsername] = useState<string>('');
+ const [password, setPassword] = useState<string>('');
+
+ const handleChangeUsername = (event: React.ChangeEvent<HTMLInputElement>) => {
+ setUsername(event.target.value);
+ };
+
+ const handleChangePassword = (event: React.ChangeEvent<HTMLInputElement>) => {
+ setPassword(event.target.value);
+ };
+
+ const handleSubmit = () => login(username, password);
- return (
+ return isAuthenticated ? null : (
<ContentSection sectionName="Login" level={1}>
Log in using your <Link href="https://edufpmi.bsu.by">EDUFPMI</Link> credentials
<p className={classes.form}>
<TextField
+ onChange={handleChangeUsername}
variant="outlined"
label="Username"
/>
<TextField
+ onChange={handleChangePassword}
variant="outlined"
label="Password"
type="password"
/>
<Button
+ onClick={handleSubmit}
variant="contained"
size="large"
color="primary"
diff --git a/src/hooks/APIClient.ts b/src/hooks/APIClient.ts
index d3c4542..015191c 100644
--- a/src/hooks/APIClient.ts
+++ b/src/hooks/APIClient.ts
@@ -6,9 +6,11 @@ type Response<T> = responseInterface<T, Error>;
const fetcher = (endpoint: string) => get(endpoint).then(response => response.data);
-
-export const useUser = (id: string): Response<User> => {
- return useSWR(id && `/users/${id}`, fetcher);
+export const useUser = (username: string | null): Response<User> => {
+ return useSWR(
+ username && `/users?username=${username}`,
+ (url: string) => get(url).then(response => response.data[0])
+ );
};
export const useEvents = (): Response<Event[]> => {
diff --git a/src/hooks/useAuth.tsx b/src/hooks/useAuth.tsx
new file mode 100644
index 0000000..39351ae
--- /dev/null
+++ b/src/hooks/useAuth.tsx
@@ -0,0 +1,70 @@
+import React, {
+ useEffect, useCallback, useMemo, useContext, createContext
+} from 'react';
+import { User } from '../types';
+import { post } from '../requests';
+import { useUser } from './APIClient';
+import useLocalStorage from './useLocalStorage';
+
+
+interface ContextType {
+ user: User | undefined,
+ login: (username: string, password: string, remember?: boolean) => Promise<boolean>;
+ logout: () => void;
+ isAuthenticated: boolean;
+}
+
+const authContext = createContext<ContextType>({
+ user: undefined,
+ login: async () => false,
+ logout: () => {},
+ isAuthenticated: false
+});
+
+const useProvideAuth = () => {
+ const [remember, setRemember] = useLocalStorage('remember');
+ const [username, setUsername] = useLocalStorage('username');
+ const [token, setToken] = useLocalStorage('token');
+ const { data: user } = useUser(username);
+
+ const isAuthenticated = useMemo(() => Boolean(username), [username]);
+
+ const logout = useCallback(() => {
+ setToken(null);
+ setUsername(null);
+ }, [setToken, setUsername]);
+
+ useEffect(() => {
+ // If should not remember, logout
+ if (!remember) logout();
+ }, [remember, logout]);
+
+
+ const login: ContextType['login'] = (name, password, shouldRemember = true) => {
+ return post('/authentication', {
+ strategy: 'local',
+ username: name,
+ password
+ }).then(response => {
+ setToken(response.data.accessToken);
+ setUsername(name);
+ setRemember(shouldRemember ? 'true' : null);
+ return true;
+ }).catch(() => false);
+ };
+
+ return {
+ user, login, logout, token, isAuthenticated
+ };
+};
+
+export const AuthProvider: React.FC = ({ children }) => {
+ const auth = useProvideAuth();
+ const { Provider } = authContext;
+ return <Provider value={auth}>{children}</Provider>;
+};
+
+export const useAuth = (): ContextType => {
+ return useContext(authContext);
+};
+
diff --git a/src/hooks/useLocalStorage.ts b/src/hooks/useLocalStorage.ts
new file mode 100644
index 0000000..a604ebe
--- /dev/null
+++ b/src/hooks/useLocalStorage.ts
@@ -0,0 +1,20 @@
+import { useState, useCallback } from 'react';
+
+type Value = string | null;
+type Setter = (value: Value) => void;
+
+const useLocalStorage = (key: string): [Value, Setter] => {
+ const [state, setState] = useState<Value>(() => localStorage.getItem(key) || null);
+
+ const update: Setter = useCallback(value => {
+ if (value) localStorage.setItem(key, value);
+ else localStorage.removeItem(key);
+ setState(value);
+ }, [key]);
+
+ return [state, update];
+};
+
+
+export default useLocalStorage;
+
diff --git a/src/index.tsx b/src/index.tsx
index 5337615..ddde049 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -5,6 +5,7 @@ import { Paper } from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import BsuFantomSection from './containers/BsuFantomSection/BsuFantomSection';
import LoginSection from './containers/LoginSection/LoginSection';
+import { AuthProvider } from './hooks/useAuth';
const useStyles = makeStyles(theme => ({
root: {
@@ -19,14 +20,16 @@ const App: React.FC = () => {
const classes = useStyles();
return (
- <Benzin>
- <Paper className={classes.root}>
- <ContentSection sectionName="famcs-kit">
- <LoginSection />
- <BsuFantomSection />
- </ContentSection>
- </Paper>
- </Benzin>
+ <AuthProvider>
+ <Benzin>
+ <Paper className={classes.root}>
+ <ContentSection sectionName="famcs-kit">
+ <LoginSection />
+ <BsuFantomSection />
+ </ContentSection>
+ </Paper>
+ </Benzin>
+ </AuthProvider>
);
};