问题
I'm trying to redirect users using permissions and conditions, but I don't know why my redirect is not working as I want. I created these kind of routes:
//not auth
<Route path="/" exact component={Login} />
//auth with admin permission
<Route path="/home" exact component={Home} isPrivate onlyAdmin />
//auth with customer permission
<Route path="/mensagens" component={Messages} isPrivate />
Then I used the props to create the redirect:
({
isPrivate = false,
onlyAdmin = false,
}) // <-- Params
//conditions
if (signed && !isPrivate && onlyAdmin && permissionType === 'customer') {
return <Redirect to="/relatorios" />;
}
if (signed && !isPrivate && !onlyAdmin && permissionType === 'admin') {
return <Redirect to="/home" />;
}
I don't know what exactly I'm doing wrong here. Using these configs, if I change the condition adding !isPrivate, nothing works, and I can go to "/" or admin routes using customer permissions. If I remove !isPrivate, only the permissions works customer can't go to admin (as I want)... but, the route "/" still works. I tried a lot of things but I didn't succeed.
回答1:
@Jonathan, I tried to make your sandbox work but most parts are broken. The nasty things with routing is one route deep inside the tree can mess up some login if not caught. Even a simple / bites you. Anyway I recreated another Sandbox with a use-case that matches your own. At least how I do it so you can get an idea, so this might be very opinionated.
Link to Live Sandbox
I am assuming you have a global state container or context where the currently logged in user and their permissions can be retrieved, and an authentication workflow that authenticates and authorizes the user. In this case, I store the current user and permission in an AuthContext wrapped around the
App Component, and eventually into the the browser's local storage. (Tons of better ways to do it).
TLDR: Main Authorization logic is in the DashboardPage component
const DashboardPage = () => {
const { currentUser } = useContext(AuthContext);
const { permissions } = currentUser;
{/*
Simplify and translate the Permissions into ways that will make sense in the UI
For example combine multiple conditions like..
const superAdmin = signed && !isPrivate && !onlyAdmin && permissionType === 'admin'
*/}
const canViewUsers = permissions.includes("view-users");
const canEditUsers = permissions.includes("edit-user");
const canManageSettings = permissions.includes("manage-settings");
return (
<>
<h1>Dashboard</h1>
<DashboardNavBar
permissions={{
canViewUsers,
canEditUsers,
canManageSettings
}}
/>
<Switch>
{/* AllUsersView wont be in the tree if canViewUsers is false.
I prefer this approach especially for SSR */}
{canViewUsers ? (
<Route path="/dashboard/all-users">
<AllUsersView />
</Route>
) : null}
{/* EditUserView still in the tree even if canEditUser is false */}
<Route path="/dashboard/edit-user">
<EditUserView canEdit={canEditUsers} />
</Route>
{canManageSettings ? (
<Route path="/dashboard/settings">
<SettingsView hasSettings={canManageSettings} />
</Route>
) : null}
<Route path="/dashboard/">
<DashboardSummary />
</Route>
{/* If false, this route will never be rendered at all e.g with SSR */}
{superAdmin? ( {/* Avoid the {superAdmin && (<Route...>) boolean */}
<Route path="/dashboard/heal-covid-19">
<SuperAdminComponent superAdmin /> {/* Prop to help redirect in component */}
</Route>
) : null} {/* Return Null and avoid booleans TS will be happy */}
{/*
Don't redirect it inline, it will force redirect all routes if false
Conditional redirect within the component render. See > 2.
*/}
{superAdmin? (
<Route path="/dashboard/settings">
<SuperAdminComponent superAdmin />
</Route>
) : <Redirect to="/home"}
</Switch>
</>
);
};
const SuperAdminComponent = ({ superAdmin }) => {
{/* >2: You can conditionally check the final condition before redirecting */}
return superAdmin ? (
<>
<h2>Super Admin</h2>
<p>Healer of Covid-19, among other things.</p>
</>
) : (
<Redirect from="/dashbaord/heal-covid-19" to="/home" />
);
};
To break it down...
First, I use a Private routes wrapper around the dashboard similar to the Redirects(Auth) example from the docs.
const AuthedComponents = () => {
return (
<Router> {/* BrowserRouter as Router */}
<div>
<UserProfile /> {/* Component with Session info like User Name, SignOut Btn */}
<MainNavBar /> {/* Publicly accessible Navigation bar Component */}
<hr />
<Switch>
<Route path="/login">
<LoginPage /> {/* The Login Page that updates the Auth Context */}
</Route>
<PrivateRoute path="/dashboard"> {/* Wrapper to redirect private routes */}
<DashboardPage />
</PrivateRoute>
{/* All other Routes outside Private are public. Add more here */}
<Route path="/">
<PublicPage />
</Route>
</Switch>
</div>
</Router>
);
};
Inside the PrivateRoute, you retrieve the login status from the AuthContext and check if the user is logged in, render the Dashboard if they are, otherwise redirect them to the login page.
// A wrapper for <Route> that redirects to the login
// screen if you're not yet authenticated.
const PrivateRoute = ({ children, ...rest }) => {
const { isLoggedIn } = useContext(AuthContext);
return (
<Route
{...rest}
render={({ location }) =>
isLoggedIn ? (
children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location } {/* Pass this along so you bring them back */}
}}
/>
)
}
/>
);
};
The AuthContext.js Component
import React, { useState } from "react";
const AuthContext = React.createContext({
isLoggedIn: false
});
export const AuthProvider = ({ children }) => {
// This example user context could be in your redux store.
const users = {
"client-1": {
id: "client-1",
username: "Client One",
permissions: ["view-users"]
},
"admin-1": {
id: "admin-1",
username: "Admin One",
permissions: ["view-users", "edit-user", "manage-settings"]
}
};
const [isLoggedIn, setIsLoggedIn] = useState(
!!localStorage.getItem("current-user")
);
const [currentUserId, setCurrentUserId] = useState(
localStorage.getItem("current-user")
);
const fakeWait = 1000;
const login = async ({ userId, history, from }) => {
await setTimeout(() => {
localStorage.setItem("current-user", userId);
setCurrentUserId(userId);
setIsLoggedIn(true);
if (from.pathname === "/" || from.pathname === "/login") {
history.push("/dashboard");
} else {
history.replace(from);
}
}, fakeWait);
return isLoggedIn;
};
const logout = async () => {
localStorage.removeItem("current-user");
await setTimeout(() => {
setIsLoggedIn(false);
}, fakeWait);
return isLoggedIn;
};
return (
<AuthContext.Provider
value={{
isLoggedIn,
login,
logout,
currentUser: users[currentUserId]
}}
>
{children}
</AuthContext.Provider>
);
};
export default AuthContext;
The App.js Component
import React, { useContext } from "react";
import {
BrowserRouter as Router,
Switch,
Route,
NavLink,
Redirect,
useHistory,
useLocation
} from "react-router-dom";
import AuthContext, { AuthProvider } from "./AuthContext";
import "./styles.scss";
const UserProfile = () => {
const { isLoggedIn, logout, currentUser } = useContext(AuthContext);
return isLoggedIn ? (
<p>
Welcome!
<span className="user-profile">{currentUser.username}</span>
<button className="signout" onClick={logout}>
Sign out
</button>
</p>
) : (
<p>You are not logged in!</p>
);
};
const MainNavBar = () => (
<ul className="navbar">
<li>
<NavLink exact to="/" activeClassName="active-link">
Public
</NavLink>
</li>
<li>
<NavLink to="/login" activeClassName="active-link">
Login
</NavLink>
</li>
<li>
<NavLink to="/dashboard" activeClassName="active-link">
Dashboard
</NavLink>
</li>
</ul>
);
const PublicPage = () => (
<>
<h1>Public</h1>
<p>Everyone can access this...</p>
</>
);
const DashboardNavBar = ({ permissions }) => (
<ul className="navbar">
<li>
<NavLink to="/dashboard/" exact activeClassName="active-link">
Welcome
</NavLink>
</li>
<li>
<NavLink to="/dashboard/all-users" activeClassName="active-link">
Users
</NavLink>
</li>
<li>
<NavLink to="/dashboard/edit-user" activeClassName="active-link">
Edit
</NavLink>
</li>
{permissions.canManageSettings && (
<li>
<NavLink to="/dashboard/settings">Settings</NavLink>
</li>
)}
</ul>
);
const DashboardSummary = () => (
<>
<h2>Summary</h2>
<p>Welcome Sceren! All Authenticated Users can access this View</p>
</>
);
const AllUsersView = () => (
<>
<h2>All users</h2>
<p>User list here. View Accessble with View Permission</p>
</>
);
const EditUserView = ({ canEdit }) =>
canEdit ? (
<>
<h2>Edit User</h2>
<p>Detais of some User to Edit</p>
<p>View Accessble with Edit Permission</p>
</>
) : (
<Redirect to="/dashboard/" />
);
const SettingsView = ({ hasSettings }) => {
return hasSettings ? (
<>
<h2>Settings</h2>
<p>View Accessble with Settings Permission</p>
</>
) : (
<Redirect from="/dashbaord/settings" to="/dashbaord" />
);
};
const DashboardPage = () => {
const { currentUser } = useContext(AuthContext);
const { permissions } = currentUser;
const canViewUsers = permissions.includes("view-users");
const canEditUsers = permissions.includes("edit-user");
const canManageSettings = permissions.includes("manage-settings");
return (
<>
<h1>Dashboard</h1>
<DashboardNavBar
permissions={{
canViewUsers,
canEditUsers,
canManageSettings
}}
/>
<Switch>
{canViewUsers ? (
<Route path="/dashboard/all-users">
<AllUsersView />
</Route>
) : null}
<Route path="/dashboard/edit-user">
<EditUserView canEdit={canEditUsers} />
</Route>
{canManageSettings ? (
<Route path="/dashboard/settings">
<SettingsView hasSettings={canManageSettings} />
</Route>
) : null}
<Route path="/dashboard/">
<DashboardSummary />
</Route>
</Switch>
</>
);
};
const LoginPage = () => {
const history = useHistory();
let location = useLocation();
const { isLoggedIn, login } = useContext(AuthContext);
const { from } = location.state || { from: { pathname: "/" } };
const { pathname } = from;
let handleLogin = userId => {
login({ userId, history, from });
};
return isLoggedIn ? (
"you are already logged in"
) : (
<div className="login-btns">
{pathname !== "/" && (
<p>You must log in to view the page at {pathname}</p>
)}
<button onClick={() => handleLogin("client-1")}>Client Logs in</button>
<button onClick={() => handleLogin("admin-1")}>Admin Logs in</button>
</div>
);
};
// A wrapper for <Route> that redirects to the login
// screen if you're not yet authenticated.
const PrivateRoute = ({ children, ...rest }) => {
const { isLoggedIn } = useContext(AuthContext);
return (
<Route
{...rest}
render={({ location }) =>
isLoggedIn ? (
children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
)
}
/>
);
};
const AuthedComponents = () => {
return (
<Router>
<div>
<h1>{` 🔐 App `}</h1>
<UserProfile />
<MainNavBar />
<hr />
<Switch>
<Route path="/login">
<LoginPage />
</Route>
<PrivateRoute path="/dashboard">
<DashboardPage />
</PrivateRoute>
<Route path="/">
<PublicPage />
</Route>
</Switch>
</div>
</Router>
);
};
const App = () => (
<AuthProvider>
<div className="App">
<AuthedComponents />
</div>
</AuthProvider>
);
export default App;
The [Styles.scss]
body {
background: #eee;
font-family: 'Sofia Pro Light', Lato, sans-serif;
font-size: 16px;
margin: 0;
padding-top: 10vh;
text-align: center;
}
.App {
height: 80vh;
padding:0;
margin: 0 auto;
width: 50%;
}
hr {
border: none;
border-bottom: solid 1px #ddd;
margin: 1rem 0 2rem;
}
.navbar {
display: flex;
justify-content: center;
list-style: none;
li {
a {
padding: 1rem 1.5rem;
text-decoration: none;
&:hover,
&:focus,
&:active {
color: #22f;
font-weight: bold;
background: #eef;
}
&.active-link{
color: red;
font-weight: bold;
}
}
}
}
.user-profile {
margin-left: 1rem;
font-weight: bolder;
}
.login-btns {
display: flex;
flex-direction: column;
button {
margin-top: 0.5rem;
}
}
button {
border: solid 1px #aaf;
cursor: pointer;
font-family: 'Sofia Pro Light', Lato, sans-serif;
font-size: 1rem;
padding: 1rem 2rem;
border-radius: 0.25rem;
&:hover,
&:focus,
&:active {
color: #22f;
font-weight: bold;
background: #eef;
}
&.signout {
padding: 0.15rem 1rem;
margin-left: 2rem;
}
}
Again link to Live Sandbox
来源:https://stackoverflow.com/questions/61915523/creating-routes-using-permissions