【问题标题】:React HOC: Redirect bug when changing from account typeReact HOC:从帐户类型更改时重定向错误
【发布时间】:2019-04-16 02:43:04
【问题描述】:

我创建了一个高阶组件WithAccess 来检查用户是否具有正确的权限(登录和帐户角色)来输入一些受保护的路由。我正在使用这样的 hoc:

const authCondition = authUser => !!authUser;

<Route path="/a" component={WithAccess(authCondition, "admin")(Admin)} />
<Route path="/u" component={WithAccess(authCondition, "user")(User)} />

Admin 和 User 是两个有路由的功能组件。

WithAccess 包含一个 onAuthStateChanged 监听器。在侦听器内部,我正在检查用户的角色(我在创建用户时设置了自定义声明“角色”)。如果这与传递的道具“角色”相匹配,则 isLoading 设置为 false 并且组件将呈现。否则用户会被重定向回登录页面。

const WithAccess = (condition, role) => Component => {
  class withAccess extends React.Component {
    state = {
      isLoading: true
    };

    componentDidMount() {
      Auth.onAuthStateChanged(async user => {
        if (user) {
          const idTokenResult = await user.getIdTokenResult(true);
          if (condition(user) && idTokenResult.claims.role === role)
            this.setState({ isLoading: false });
          else this.props.history.push(`/login`);
        }
      });
    }

    render() {
      return !this.state.isLoading ? (
        <Component />
      ) : (
        <PageLoader label="Checking user..." />
      );
    }
  }

  return withRouter(withAccess);
};

export default WithAccess;

这是有效的。但是,当我从管理员帐户切换到用户帐户或反之亦然时,WithAccess 将传递以前的角色。

为了解决问题,我在代码沙箱中重新制作了登录/注册部分:link to code sandbox

最佳繁殖方式:

  1. 进入登录页面并注册一个新用户
  2. 重定向到用户仪表板时注销
  3. 以管理员身份登录,邮箱为:admin@example.com,密码:123456
  4. “登录”将在导航中更改为“仪表板”,但您将停留在登录页面上(事实上,您会被重定向到 /a/dashboard,但 WithAccess 会立即将您重定向回来)

我试图理解为什么 WithAccess 在从帐户类型切换时会传递以前的角色,但我还没有弄清楚。

【问题讨论】:

  • 我试图重现,但它按预期正常工作!我没有从仪表板重定向。
  • 嗯,这是一个好消息和坏消息哈哈。也许这是我浏览器中的特定内容
  • 我能够重现错误。

标签: javascript reactjs firebase redux firebase-authentication


【解决方案1】:

您的应用程序中存在内存泄漏!抓住这些非常棘手:) https://codesandbox.io/s/k3218vqq8o

在您的 With Access HOC 中,您必须不听 firebase onAuth 回调。

如果您在某处添加监听器,请务必记得清理组件!

componentDidMount() {
  //Retain a reference to the unlistener callback
  this.fireBaseAuthUnlistener = Auth.onAuthStateChanged(async user => {
    if (user) {
      const idTokenResult = await user.getIdTokenResult(true);
      console.log("ID Token claims: ", idTokenResult.claims);
      console.log("ROLE : ", role);
      if (condition(user) && idTokenResult.claims.role === role) {
        this.setState({ isLoading: false });
        console.log(user);
      } else {
        this.props.history.push(`/login`);
      }
    }
  });
}

componentWillUnmount() {
  //Unlisten when unmounting!
  this.fireBaseAuthUnlistener && this.fireBaseAuthUnlistener();
  this.fireBaseAuthUnlistener = undefined;
}

【讨论】:

  • 我想就是这样。添加了 unlistener 并尝试使用不同的帐户类型注册和登录几次。到目前为止没有错误!
  • @Thore 是的,很可能是这样!如果这回答了您的问题,请不要忘记标记为已回答 :)
【解决方案2】:

我认为您应该大大简化您的路由结构,首先将经过身份验证的路由包装在 ProtectedRoutes HOC 中。此路由仅允许经过身份验证(登录)的用户查看它。然后添加另一个RequireAdmin HOC 以检查role 是否为admin。如果没有,它会重定向到NotFound 页面。

经过身份验证的用户可以访问/u/dashboard,但是当他们单击Admin Dashboard 链接时,它会显示NotFound 页面。但是,管理员可以访问两者!

测试:

  • 点击Login
  • testuser123@example.com 登录,密码123456
  • 重定向时,尝试访问Admin Dashboard
  • 重定向到NotFound页面
  • 点击Go Back
  • 点击Dashboard
  • 点击Logout
  • 再次点击Login
  • admin@example.com 登录,密码123456
  • 重定向时,点击User DashboardAdmin Dashboard(都可以)

工作示例


容器/ProtectedRoutes

import isEmpty from "lodash/isEmpty";
import React, { Component, Fragment } from "react";
import { connect } from "react-redux";
import { withRouter } from "react-router-dom";
import Navigation from "../../components/Website/Navigation";

class ProtectedRoutes extends Component {
  componentDidMount = () => {
   if (isEmpty(this.props.user) || !this.props.user.email) {
      this.props.history.push("/notfound");
    }
  };

  componentDidUpdate = prevProps => {
    if (this.props.user !== prevProps.user) {
      this.props.history.push("/notfound");
    }
  };

  render = () =>
    this.props.user.email ? (
      <Fragment>
        <Navigation />
        {this.props.children}
      </Fragment>
    ) : null;
}

export default connect(state => ({ user: state.user }))(
  withRouter(ProtectedRoutes)
);

容器/RequireAdmin

import isEmpty from "lodash/isEmpty";
import { Component } from "react";
import { connect } from "react-redux";
import { withRouter } from "react-router-dom";

class RequireAdmin extends Component {
  componentDidMount = () => {
    if (isEmpty(this.props.user) || this.props.user.role !== "admin") {
      this.props.history.push("/notfound");
    }
  };

  componentDidUpdate = prevProps => {
    if (this.props.user !== prevProps.user) {
      this.props.history.push("/notfound");
    }
  };

  render = () =>
    isEmpty(this.props.user) || this.props.user.role === "admin"
      ? this.props.children
      : null;
}

export default connect(state => ({ user: state.user }))(
  withRouter(RequireAdmin)
);

路线(主要)

import React from "react";
import { Route, Switch } from "react-router-dom";
import ProtectedRoutes from "../containers/ProtectedRoutes";

import Admin from "../components/Admin";
import Home from "../pages/Website/Home";
import NotFound from "../pages/Website/NotFound";
import LoginForm from "../containers/LoginForm";
import RegisterForm from "../containers/RegisterForm";
import User from "../components/User";

const Routes = () => (
  <div>
    <Switch>
      <Route exact path="/" component={Home} />
      <Route exact path="/login" component={LoginForm} />
      <Route exact path="/register" component={RegisterForm} />
      <Route exact path="/notfound" component={NotFound} />
      <ProtectedRoutes>
        <Route path="/u" component={User} />
        <Route path="/a" component={Admin} />
      </ProtectedRoutes>
    </Switch>
  </div>
);

export default Routes;

组件/用户(用户保护的路由)

import React, { Fragment } from "react";
import { Route, Switch } from "react-router-dom";
import Dashboard from "../../pages/User/Dashboard";
import NotFound from "../../pages/Website/NotFound";

const User = ({ match }) => (
  <Fragment>
    <Switch>
      <Route exact path={`${match.url}/dashboard`} component={Dashboard} />
      <Route path={`${match.url}`} component={NotFound} />
    </Switch>
  </Fragment>
);

export default User;

components/Admin(受管理员保护的路由)

import React, { Fragment } from "react";
import { Route, Switch } from "react-router-dom";
import RequireAdmin from "../../containers/RequireAdmin";
import Dashboard from "../../pages/Admin/Dashboard";
import NotFound from "../../pages/Website/NotFound";

const Admin = ({ match }) => (
  <RequireAdmin>
    <Switch>
      <Route exact path={`${match.url}/dashboard`} component={Dashboard} />
      <Route path={`${match.url}`} component={NotFound} />
    </Switch>
  </RequireAdmin>
);

export default Admin;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-18
    • 2018-04-17
    • 1970-01-01
    • 2021-10-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多