-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: auth two electric boogaloo (#16)
* feat: auth middleware * feat: flag middleware behind prod node_env * chore: fix comment * fix: misc. * refactor: misc. * feat: implement verifyRoute middleware * feat: role context, protected routes by route * feat: simplify App * feat: add example usage of protected route * feat: better validation for signup/login * docs: misc. * feat: create db user on signin/login * feat: extract redirect handling to hook * feat: use backend context in role context
- Loading branch information
1 parent
10a24d5
commit e3ff3f9
Showing
23 changed files
with
476 additions
and
227 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,36 @@ | ||
// import { useEffect, useState } from "react"; | ||
|
||
import { Navigate } from "react-router-dom"; | ||
|
||
import { useAuthContext } from "../contexts/hooks/useAuthContext"; | ||
import { useRoleContext } from "../contexts/hooks/useRoleContext"; | ||
|
||
interface ProtectedRouteProps { | ||
element: JSX.Element; | ||
allowedRoles?: string | string[]; | ||
} | ||
|
||
export const ProtectedRoute = ({ element }: ProtectedRouteProps) => { | ||
export const ProtectedRoute = ({ | ||
element, | ||
allowedRoles = [], | ||
}: ProtectedRouteProps) => { | ||
const { currentUser } = useAuthContext(); | ||
// const [isLoading, setIsLoading] = useState(true); | ||
|
||
// useEffect(() => { | ||
// setIsLoading(false); | ||
// }, []); | ||
|
||
// if (isLoading) { | ||
// return <h1>Loading...</h1>; | ||
// } | ||
const { role } = useRoleContext(); | ||
|
||
return currentUser ? element : <Navigate to={"/login"} />; | ||
const roles = Array.isArray(allowedRoles) ? allowedRoles : [allowedRoles]; | ||
const isValidRole = getIsValidRole(roles, role); | ||
return currentUser && isValidRole ? element : <Navigate to={"/login"} />; | ||
}; | ||
|
||
/** | ||
* Helper function for determining if a user may access a route based on their role. | ||
* If no allowed roles are specified, or if the user is an admin, they are authorized. Otherwise, their role must be within the list of allowed roles. | ||
* | ||
* @param roles a list of roles which may access this route | ||
* @param role the current user's role | ||
*/ | ||
function getIsValidRole(roles: string[], role: string | undefined) { | ||
return ( | ||
roles.length === 0 || | ||
(role !== undefined && roles.includes(role)) || | ||
role === "admin" | ||
); | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { Box, Text } from "@chakra-ui/react"; | ||
|
||
export const Admin = () => { | ||
return ( | ||
<Box> | ||
<Text>This is a page only admins are able to see!</Text> | ||
</Box> | ||
); | ||
}; |
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { ChangeEvent, useCallback, useState } from "react"; | ||
|
||
import { Select, Spinner, useToast } from "@chakra-ui/react"; | ||
|
||
import { useBackendContext } from "../../contexts/hooks/useBackendContext"; | ||
import { User } from "../../types/user"; | ||
|
||
interface RoleSelectProps { | ||
user: User; | ||
disabled?: boolean; | ||
} | ||
|
||
export const RoleSelect = ({ user, disabled = true }: RoleSelectProps) => { | ||
const { backend } = useBackendContext(); | ||
const toast = useToast(); | ||
|
||
const [role, setRole] = useState(user.role); | ||
const [loading, setLoading] = useState(false); | ||
|
||
const handleChangeRole = useCallback( | ||
async (e: ChangeEvent<HTMLSelectElement>) => { | ||
const previousRole = role; | ||
const updatedRole = e.currentTarget.value; | ||
setLoading(true); | ||
|
||
try { | ||
await backend.put("/users/update/set-role", { | ||
role: updatedRole, | ||
firebaseUid: user.firebaseUid, | ||
}); | ||
|
||
if (updatedRole !== "user" && updatedRole !== "admin") { | ||
throw Error("Role is not valid"); | ||
} | ||
|
||
setRole(updatedRole); | ||
|
||
toast({ | ||
title: "Role Updated", | ||
description: `Updated role from ${previousRole} to ${updatedRole}`, | ||
status: "success", | ||
}); | ||
} catch (error) { | ||
console.error("Error updating user role:", error); | ||
|
||
toast({ | ||
title: "An Error Occurred", | ||
description: `Role was not updated`, | ||
status: "error", | ||
}); | ||
} finally { | ||
setLoading(false); | ||
} | ||
}, | ||
[backend, role, toast, user.firebaseUid] | ||
); | ||
|
||
return ( | ||
<Select | ||
placeholder="Select role" | ||
value={role} | ||
onChange={handleChangeRole} | ||
disabled={loading || disabled} | ||
icon={loading ? <Spinner size={"xs"} /> : undefined} | ||
> | ||
<option value="user">User</option> | ||
<option value="admin">Admin</option> | ||
</Select> | ||
); | ||
}; |
Oops, something went wrong.