diff options
author | eug-vs <eug-vs@keemail.me> | 2020-11-15 04:07:33 +0300 |
---|---|---|
committer | eug-vs <eug-vs@keemail.me> | 2020-11-15 04:07:33 +0300 |
commit | 81b46581ab79f8f5e9e132e00e5d1b8e9182dd46 (patch) | |
tree | 7c46c3fd9b2b66af20ce91b61ac889d327c1e492 | |
parent | eb2e929e2188ccfd9f575a5aa425024bf1fec67f (diff) | |
download | famcs-kit-81b46581ab79f8f5e9e132e00e5d1b8e9182dd46.tar.gz |
feat: implement authentication
-rw-r--r-- | src/containers/LoginSection/LoginSection.tsx | 22 | ||||
-rw-r--r-- | src/hooks/APIClient.ts | 8 | ||||
-rw-r--r-- | src/hooks/useAuth.tsx | 70 | ||||
-rw-r--r-- | src/hooks/useLocalStorage.ts | 20 | ||||
-rw-r--r-- | src/index.tsx | 19 |
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> ); }; |