Fork of the excellent esp8266-react - https://github.com/rjwats/esp8266-react
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

109 lines
3.3 KiB

  1. import * as React from 'react';
  2. import { withSnackbar, WithSnackbarProps } from 'notistack';
  3. import jwtDecode from 'jwt-decode';
  4. import history from '../history'
  5. import { VERIFY_AUTHORIZATION_ENDPOINT } from '../api';
  6. import { ACCESS_TOKEN, authorizedFetch, getStorage } from './Authentication';
  7. import { AuthenticationContext, Me } from './AuthenticationContext';
  8. import FullScreenLoading from '../components/FullScreenLoading';
  9. import { withFeatures, WithFeaturesProps } from '../features/FeaturesContext';
  10. export const decodeMeJWT = (accessToken: string): Me => jwtDecode(accessToken);
  11. interface AuthenticationWrapperState {
  12. context: AuthenticationContext;
  13. initialized: boolean;
  14. }
  15. type AuthenticationWrapperProps = WithSnackbarProps & WithFeaturesProps;
  16. class AuthenticationWrapper extends React.Component<AuthenticationWrapperProps, AuthenticationWrapperState> {
  17. constructor(props: AuthenticationWrapperProps) {
  18. super(props);
  19. this.state = {
  20. context: {
  21. refresh: this.refresh,
  22. signIn: this.signIn,
  23. signOut: this.signOut,
  24. },
  25. initialized: false
  26. };
  27. }
  28. componentDidMount() {
  29. this.refresh();
  30. }
  31. render() {
  32. return (
  33. <React.Fragment>
  34. {this.state.initialized ? this.renderContent() : this.renderContentLoading()}
  35. </React.Fragment>
  36. );
  37. }
  38. renderContent() {
  39. return (
  40. <AuthenticationContext.Provider value={this.state.context}>
  41. {this.props.children}
  42. </AuthenticationContext.Provider>
  43. );
  44. }
  45. renderContentLoading() {
  46. return (
  47. <FullScreenLoading />
  48. );
  49. }
  50. refresh = () => {
  51. if (!this.props.features.security) {
  52. this.setState({ initialized: true, context: { ...this.state.context, me: { admin: true, username: "admin" } } });
  53. return;
  54. }
  55. const accessToken = getStorage().getItem(ACCESS_TOKEN)
  56. if (accessToken) {
  57. authorizedFetch(VERIFY_AUTHORIZATION_ENDPOINT)
  58. .then(response => {
  59. const me = response.status === 200 ? decodeMeJWT(accessToken) : undefined;
  60. this.setState({ initialized: true, context: { ...this.state.context, me } });
  61. }).catch(error => {
  62. this.setState({ initialized: true, context: { ...this.state.context, me: undefined } });
  63. this.props.enqueueSnackbar("Error verifying authorization: " + error.message, {
  64. variant: 'error',
  65. });
  66. });
  67. } else {
  68. this.setState({ initialized: true, context: { ...this.state.context, me: undefined } });
  69. }
  70. }
  71. signIn = (accessToken: string) => {
  72. try {
  73. getStorage().setItem(ACCESS_TOKEN, accessToken);
  74. const me: Me = decodeMeJWT(accessToken);
  75. this.setState({ context: { ...this.state.context, me } });
  76. this.props.enqueueSnackbar(`Logged in as ${me.username}`, { variant: 'success' });
  77. } catch (err) {
  78. this.setState({ initialized: true, context: { ...this.state.context, me: undefined } });
  79. throw new Error("Failed to parse JWT " + err.message);
  80. }
  81. }
  82. signOut = () => {
  83. getStorage().removeItem(ACCESS_TOKEN);
  84. this.setState({
  85. context: {
  86. ...this.state.context,
  87. me: undefined
  88. }
  89. });
  90. this.props.enqueueSnackbar("You have signed out.", { variant: 'success', });
  91. history.push('/');
  92. }
  93. }
  94. export default withFeatures(withSnackbar(AuthenticationWrapper))