Created
November 11, 2019 23:35
-
-
Save kalda341/a32b9a9311643756d7db69b239743cb2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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