Skip to content

Instantly share code, notes, and snippets.

@kalda341
Created November 11, 2019 23:35
Show Gist options
  • Save kalda341/a32b9a9311643756d7db69b239743cb2 to your computer and use it in GitHub Desktop.
Save kalda341/a32b9a9311643756d7db69b239743cb2 to your computer and use it in GitHub Desktop.
export function AuthProvider({
loadToken,
saveToken,
requestToken,
refreshToken,
children
}) {
const [loadingState, dispatchLoading] = useLoadingReducer({
isLoading: true
});
const loadAuthenticationTask = useTask(function*() {
dispatchLoading({
type: 'LOADING',
message: 'Loading auth token...'
});
let loadedToken = yield loadToken();
if (isNil(loadedToken)) {
setTokenTask.perform(null);
} else if (getTokenEpiry(loadedToken.access) - EXPIRY_OFFSET > 0) {
// Use the token we have if it hasn't expired
setTokenTask.perform(loadedToken);
} else {
// We need to update the token. We might as well try a refresh even
// if the refresh token is expired.
refreshTokenTask.perform(loadedToken);
}
}, 'RESTARTABLE');
useEffect(() => {
loadAuthenticationTask.perform();
}, []);
const setTokenTask = useTask(function*(token) {
yield saveToken(token);
if (!isNil(token)) {
dispatchLoading({ type: 'SUCCESS', data: token });
// Schedule token refresh any time we update the token
// Don't yield on this!
refreshTokenTask.perform(token);
} else {
dispatchLoading({
type: 'ERROR',
message: 'Not authenticated'
});
// We don't want a refresh to replace the token!
refreshTokenTask.cancelAll();
}
}, 'RESTARTABLE');
const refreshTokenTask = useTask(function*(token) {
const timeUntilRefresh =
(getTokenEpiry(token.access) - EXPIRY_OFFSET) * 1000;
// Wait until we need to refresh, if we don't need to immediately
if (timeUntilRefresh > 0) {
yield timeout(timeUntilRefresh);
}
try {
const newToken = merge(token, yield refreshToken(token));
// Note - no yield as it will call refreshTokenTask
setTokenTask.perform(newToken);
} catch (error) {
console.log('Failed to refresh access token');
// eslint-disable-next-line no-console
console.log(error);
setTokenTask.perform(null);
}
}, 'RESTARTABLE');
const authenticationTask = useTask(function*(email, password) {
try {
const token = yield requestToken({ username: email, password });
yield setTokenTask.perform(token);
} catch (error) {
setTokenTask.perform(null);
if (error.status === 401) {
throw { errorText: 'Invalid email or password' };
} else {
throw { errorText: 'An unknown error occured' };
}
}
}, 'RESTARTABLE');
const authHeaders = compose(
unless(isNil, t => ({ Authorization: `Bearer ${t}` })),
prop('access')
)(loadingState.data);
return (
<AuthContext.Provider
value={{
authenticate: (email, password) =>
authenticationTask.perform(email, password).getPromise(),
unauthenticate: () => setTokenTask.perform(null).getPromise(),
isAuthenticated: loadingState.isSuccess,
isLoading: loadingState.isLoading,
authHeaders
}}
>
{children}
</AuthContext.Provider>
);
}
AuthProvider.propTypes = {
loadToken: PropTypes.func.isRequired,
saveToken: PropTypes.func.isRequired,
requestToken: PropTypes.func.isRequired,
refreshToken: PropTypes.func.isRequired,
children: PropTypes.any.isRequired
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment