-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This PR adds in support for authentication to the admin side of Otto. This includes a sign-in page, user model, me routes, and a AuthContext for rendering the authenticated user state. This also includes a change to the API to change how the default routing was being handled. This was written by [email protected]. Signed-off-by: tylerslaton <[email protected]> Co-authored-by: Donnie Adams <[email protected]>
- Loading branch information
1 parent
faefe47
commit 071234d
Showing
13 changed files
with
258 additions
and
12 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 |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { ReactNode, createContext, useContext } from "react"; | ||
import useSWR from "swr"; | ||
|
||
import { Role, User } from "~/lib/model/users"; | ||
import { UserService } from "~/lib/service/api/userService"; | ||
|
||
interface AuthContextType { | ||
me: User; | ||
isLoading: boolean; | ||
} | ||
|
||
const AuthContext = createContext<AuthContextType | undefined>(undefined); | ||
|
||
export function AuthProvider({ children }: { children: ReactNode }) { | ||
const { data: me, isLoading } = useSWR( | ||
UserService.getMe.key(), | ||
() => UserService.getMe(), | ||
{ fallbackData: { role: Role.Default } as User } | ||
); | ||
|
||
return ( | ||
<AuthContext.Provider value={{ me, isLoading }}> | ||
{children} | ||
</AuthContext.Provider> | ||
); | ||
} | ||
|
||
export function useAuth() { | ||
const context = useContext(AuthContext); | ||
if (context === undefined) { | ||
throw new Error("useAuth must be used within a AuthProvider"); | ||
} | ||
return context; | ||
} |
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,47 @@ | ||
import React from "react"; | ||
import { FaGoogle } from "react-icons/fa"; | ||
|
||
import { cn } from "~/lib/utils"; | ||
|
||
import { OttoLogo } from "~/components/branding/OttoLogo"; | ||
import { Button } from "~/components/ui/button"; | ||
import { | ||
Card, | ||
CardDescription, | ||
CardFooter, | ||
CardHeader, | ||
CardTitle, | ||
} from "~/components/ui/card"; | ||
|
||
interface SignInProps { | ||
className?: string; | ||
} | ||
|
||
const SignIn: React.FC<SignInProps> = ({ className }) => { | ||
return ( | ||
<Card className={cn("flex flex-col justify-between", className)}> | ||
<CardHeader> | ||
<CardTitle className="flex items-center justify-center"> | ||
<OttoLogo /> | ||
</CardTitle> | ||
<CardDescription className="text-center w-3/4 mx-auto pt-4"> | ||
Please sign in using the button below. | ||
</CardDescription> | ||
</CardHeader> | ||
<CardFooter className="border-t pt-4"> | ||
<Button | ||
variant="destructive" | ||
className="w-full" | ||
onClick={() => { | ||
window.location.href = "/oauth2/start?rd=/admin/"; | ||
}} | ||
> | ||
<FaGoogle className="mr-2" /> | ||
Sign In with Google | ||
</Button> | ||
</CardFooter> | ||
</Card> | ||
); | ||
}; | ||
|
||
export default SignIn; |
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,65 @@ | ||
import { User } from "lucide-react"; | ||
import React from "react"; | ||
|
||
import { roleToString } from "~/lib/model/users"; | ||
import { cn } from "~/lib/utils"; | ||
|
||
import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar"; | ||
import { | ||
Popover, | ||
PopoverContent, | ||
PopoverTrigger, | ||
} from "~/components/ui/popover"; | ||
|
||
import { useAuth } from "../auth/AuthContext"; | ||
import { Button } from "../ui/button"; | ||
|
||
interface UserMenuProps { | ||
className?: string; | ||
} | ||
|
||
export const UserMenu: React.FC<UserMenuProps> = ({ className }) => { | ||
const { me } = useAuth(); | ||
|
||
if (me.username === "nobody") { | ||
return null; | ||
} | ||
|
||
return ( | ||
<Popover> | ||
<PopoverTrigger asChild> | ||
<div | ||
className={cn( | ||
"flex items-center cursor-pointer", | ||
className | ||
)} | ||
> | ||
<Avatar className="mr-4"> | ||
<AvatarImage /> | ||
<AvatarFallback> | ||
<User className="w-5 h-5" /> | ||
</AvatarFallback> | ||
</Avatar> | ||
<div className="truncate max-w-full"> | ||
<p className="text-sm font-medium truncate"> | ||
{me?.email} | ||
</p> | ||
<p className="text-muted-foreground text-left text-xs truncate"> | ||
{roleToString(me?.role)} | ||
</p> | ||
</div> | ||
</div> | ||
</PopoverTrigger> | ||
<PopoverContent className="w-auto" side="bottom"> | ||
<Button | ||
variant="destructive" | ||
onClick={() => { | ||
window.location.href = "/oauth2/sign_out?rd=/admin/"; | ||
}} | ||
> | ||
Sign Out | ||
</Button> | ||
</PopoverContent> | ||
</Popover> | ||
); | ||
}; |
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,29 @@ | ||
export type User = { | ||
id: number; | ||
createdAt: Date; | ||
username: string; | ||
email: string; | ||
role: Role; | ||
}; | ||
|
||
export const Role = { | ||
Admin: 1, | ||
Default: 2, | ||
} as const; | ||
export type Role = (typeof Role)[keyof typeof Role]; | ||
|
||
export function roleToString(role: Role): string { | ||
return ( | ||
Object.keys(Role).find( | ||
(key) => Role[key as keyof typeof Role] === role | ||
) || "Unknown" | ||
); | ||
} | ||
|
||
export function stringToRole(roleStr: string): Role { | ||
const role = Role[roleStr as keyof typeof Role]; | ||
if (role === undefined) { | ||
throw new Error(`Invalid role string: ${roleStr}`); | ||
} | ||
return role; | ||
} |
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,21 @@ | ||
import { User } from "~/lib/model/users"; | ||
import { ApiRoutes, revalidateWhere } from "~/lib/routers/apiRoutes"; | ||
import { request } from "~/lib/service/api/primitives"; | ||
|
||
async function getMe() { | ||
const res = await request<User>({ | ||
url: ApiRoutes.me().url, | ||
errorMessage: "Failed to fetch agents", | ||
}); | ||
|
||
return res.data; | ||
} | ||
getMe.key = () => ({ url: ApiRoutes.me().path }) as const; | ||
|
||
const revalidateMe = () => | ||
revalidateWhere((url) => url.includes(ApiRoutes.me().path)); | ||
|
||
export const UserService = { | ||
getMe, | ||
revalidateMe, | ||
}; |
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
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 SignIn from "~/components/signin/SignIn"; | ||
|
||
export default function SignInPage() { | ||
return ( | ||
<div className="flex min-h-screen w-full items-center justify-center p-4"> | ||
<SignIn className="w-full max-w-md" /> | ||
</div> | ||
); | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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