mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-07 02:18:07 +01:00
Auth! (#11347)
* reload the window on 401 * backend apis for auth * add login page * re-enable web linter * fix login page routing * bypass csrf for internal auth endpoint * disable healthcheck in devcontainer target * include login page in vite build * redirect to login page on 401 * implement config for users and settings * implement JWT actual secret * add brute force protection on login * add support for redirecting from auth failures on api calls * return location for redirect * default cookie name should pass regex test * set hash iterations to current OWASP recommendation * move users to database instead of config * config option to reset admin password on startup * user management UI * check for deleted user on refresh * validate username and fixes * remove password constraint * cleanup * fix user check on refresh * web fixes * implement auth via new external port * use x-forwarded-for to rate limit login attempts by ip * implement logout and profile * fixes * lint fixes * add support for user passthru from upstream proxies * add support for specifying a logout url * add documentation * Update docs/docs/configuration/authentication.md Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com> * Update docs/docs/configuration/authentication.md Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com> --------- Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
This commit is contained in:
132
web/src/components/auth/AuthForm.tsx
Normal file
132
web/src/components/auth/AuthForm.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||
import axios, { AxiosError } from "axios";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
} from "@/components/ui/form";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
interface UserAuthFormProps extends React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
|
||||
const [isLoading, setIsLoading] = React.useState<boolean>(false);
|
||||
|
||||
const formSchema = z.object({
|
||||
user: z.string(),
|
||||
password: z.string(),
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
mode: "onChange",
|
||||
defaultValues: {
|
||||
user: "",
|
||||
password: "",
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = async (values: z.infer<typeof formSchema>) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await axios.post(
|
||||
"/api/login",
|
||||
{
|
||||
user: values.user,
|
||||
password: values.password,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"X-CSRF-TOKEN": 1,
|
||||
},
|
||||
},
|
||||
);
|
||||
window.location.href = "/";
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const err = error as AxiosError;
|
||||
if (err.response?.status === 429) {
|
||||
toast.error("Exceeded rate limit. Try again later.", {
|
||||
position: "top-center",
|
||||
});
|
||||
} else if (err.response?.status === 400) {
|
||||
toast.error("Login failed", {
|
||||
position: "top-center",
|
||||
});
|
||||
} else {
|
||||
toast.error("Unknown error. Check logs.", {
|
||||
position: "top-center",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
toast.error("Unknown error. Check console logs.", {
|
||||
position: "top-center",
|
||||
});
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cn("grid gap-6", className)} {...props}>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<FormField
|
||||
name="user"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>User</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
|
||||
type="password"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="flex flex-row gap-2 pt-5">
|
||||
<Button
|
||||
variant="select"
|
||||
disabled={isLoading}
|
||||
className="flex flex-1"
|
||||
>
|
||||
{isLoading && <ActivityIndicator className="mr-2 h-4 w-4" />}
|
||||
Login
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
<Toaster />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user