clean up authentication code

This commit is contained in:
Michael Fatemi 2021-07-03 00:55:13 -04:00
parent 557d554db3
commit dde3edee04
4 changed files with 53 additions and 63 deletions

View File

@ -14,10 +14,10 @@ const ION_AUTHORIZATION_ENDPOINT = dev
: 'https://ion.tjhsst.edu/oauth/authorize?response_type=code&client_id=rNa6n9YSg8ftINdyVPpUsaMuxNbHLo9dh1OsOktR&scope=read&redirect_uri=https%3A%2F%2Fwheelshare.space%2Fauth%2Fion%2Fcallback';
export default function App() {
const { isLoggedIn, user } = useContext(AuthenticationContext);
const { user } = useContext(AuthenticationContext);
return (
<div style={{ padding: '1rem' }}>
{isLoggedIn ? (
{user ? (
<div
style={{
display: 'flex',
@ -28,7 +28,7 @@ export default function App() {
alignItems: 'center',
}}
>
{user!.name}{' '}
{user.name}{' '}
<UIButton style={{ marginTop: 0 }} onClick={logout}>
Log out
</UIButton>

View File

@ -6,20 +6,19 @@ export type User = {
email?: string;
};
export type AuthState = {
isLoggedIn: boolean | null;
export type AuthenticationContextProps = {
user: User | null;
/**
* Function that can be used to trigger an auth state refresh.
*/
refreshAuthState: (() => void) | null;
refresh: () => void;
};
const AuthenticationContext = createContext<AuthState>({
isLoggedIn: false,
const AuthenticationContext = createContext<AuthenticationContextProps>({
user: null,
refreshAuthState: null,
refresh: () =>
console.warn('calling refresh on default AuthenticationContext'),
});
export default AuthenticationContext;

View File

@ -1,45 +1,30 @@
import { useCallback, useEffect, useState } from 'react';
import { getMe } from '../api';
import AuthenticationContext, { AuthState } from './AuthenticationContext';
import AuthenticationContext, { User } from './AuthenticationContext';
export default function AuthenticationWrapper({
children,
}: {
children: React.ReactNode;
}) {
const sessionToken = localStorage.getItem('session_token');
// Prevent race conditions
const [authState, setAuthState] = useState<AuthState>({
isLoggedIn: null,
user: null,
refreshAuthState: null,
});
const [user, setUser] = useState<User | null>(null);
const refreshAuthState = useCallback(() => {
const loggedOut = () =>
setAuthState({ isLoggedIn: false, user: null, refreshAuthState });
const refresh = useCallback(() => {
const none = () => setUser(null);
const sessionToken = localStorage.getItem('session_token');
if (sessionToken) {
getMe()
.then((user) => {
if (user) {
setAuthState({ isLoggedIn: true, user, refreshAuthState });
getMe().then(setUser).catch(none);
} else {
loggedOut();
none();
}
})
.catch(loggedOut);
} else {
loggedOut();
}
}, [sessionToken]);
}, []);
useEffect(() => {
refreshAuthState();
}, [refreshAuthState]);
useEffect(refresh, [refresh]);
return (
<AuthenticationContext.Provider value={authState}>
<AuthenticationContext.Provider value={{ user, refresh }}>
{children}
</AuthenticationContext.Provider>
);

View File

@ -3,45 +3,51 @@ import { Redirect, useLocation, useParams } from 'react-router-dom';
import AuthenticationContext from './AuthenticationContext';
import { createSession } from './createSession';
function useCode() {
const location = useLocation();
const query = new URLSearchParams(location.search);
const code = query.get('code');
return code;
}
export default function Authenticator() {
const { provider } = useParams<{ provider: string }>();
const query = new URLSearchParams(useLocation().search);
const code = query.get('code');
const { refreshAuthState } = useContext(AuthenticationContext);
const [status, setStatus] =
useState<'pending' | 'errored' | 'authenticated'>('pending');
const code = useCode();
const { refresh } = useContext(AuthenticationContext);
const [pending, setPending] = useState(true);
const [token, setToken] = useState<string | null>(null);
useEffect(() => {
createSession(code!)
.then((data) => {
if (data.status === 'success') {
console.log('Success! Token:', data.token);
setToken(data.token);
localStorage.setItem('session_token', data.token);
setStatus('authenticated');
if (token) {
localStorage.setItem('session_token', token);
} else {
console.log('Authentication failure.');
setToken(null);
localStorage.removeItem('session_token');
setStatus('errored');
}
}, [token]);
useEffect(() => {
setPending(true);
createSession(code!)
.then(({ token }) => {
setToken(token ?? null);
})
.catch(() => {
setStatus('errored');
});
.finally(() => setPending(false));
}, [code, provider]);
useEffect(() => {
refreshAuthState && refreshAuthState();
}, [token, refreshAuthState]);
refresh();
}, [token, refresh]);
switch (status) {
case 'authenticated':
return <Redirect to="/" />;
case 'errored':
return <h1>Sign In Error</h1>;
case 'pending':
if (pending) {
return <h1>Signing In</h1>;
}
if (token) {
return <Redirect to="/" />;
}
// If we aren't pending anymore, but don't have a token, we must have errored
return <h1>Sign In Error</h1>;
}