mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-10-28 12:45:47 +00:00
225 lines
6.5 KiB
TypeScript
225 lines
6.5 KiB
TypeScript
import {
|
|
Button,
|
|
Paper,
|
|
PasswordInput,
|
|
TextInput,
|
|
Title,
|
|
Text,
|
|
Divider,
|
|
Grid,
|
|
} from "@mantine/core";
|
|
import { useForm, zodResolver } from "@mantine/form";
|
|
import { notifications } from "@mantine/notifications";
|
|
import { useMutation } from "@tanstack/react-query";
|
|
import axios from "axios";
|
|
import { z } from "zod";
|
|
import { useUserContext } from "../context/user-context";
|
|
import { Navigate } from "react-router";
|
|
import { Layout } from "../components/layouts/layout";
|
|
import { GoogleIcon } from "../icons/google";
|
|
import { GithubIcon } from "../icons/github";
|
|
import { OAuthIcon } from "../icons/oauth";
|
|
import { TailscaleIcon } from "../icons/tailscale";
|
|
import { isQueryValid } from "../utils/utils";
|
|
|
|
export const LoginPage = () => {
|
|
const queryString = window.location.search;
|
|
const params = new URLSearchParams(queryString);
|
|
const redirectUri = params.get("redirect_uri") ?? "";
|
|
|
|
const { isLoggedIn, configuredProviders } = useUserContext();
|
|
|
|
const oauthProviders = configuredProviders.filter(
|
|
(value) => value !== "username",
|
|
);
|
|
|
|
if (isLoggedIn) {
|
|
return <Navigate to="/logout" />;
|
|
}
|
|
|
|
const schema = z.object({
|
|
username: z.string(),
|
|
password: z.string(),
|
|
});
|
|
|
|
type FormValues = z.infer<typeof schema>;
|
|
|
|
const form = useForm({
|
|
mode: "uncontrolled",
|
|
initialValues: {
|
|
username: "",
|
|
password: "",
|
|
},
|
|
validate: zodResolver(schema),
|
|
});
|
|
|
|
const loginMutation = useMutation({
|
|
mutationFn: (login: FormValues) => {
|
|
return axios.post("/api/login", login);
|
|
},
|
|
onError: () => {
|
|
notifications.show({
|
|
title: "Failed to login",
|
|
message: "Check your username and password",
|
|
color: "red",
|
|
});
|
|
},
|
|
onSuccess: () => {
|
|
notifications.show({
|
|
title: "Logged in",
|
|
message: "Welcome back!",
|
|
color: "green",
|
|
});
|
|
setTimeout(() => {
|
|
if (!isQueryValid(redirectUri)) {
|
|
window.location.replace("/");
|
|
} else {
|
|
window.location.replace(`/continue?redirect_uri=${redirectUri}`);
|
|
}
|
|
}, 500);
|
|
},
|
|
});
|
|
|
|
const loginOAuthMutation = useMutation({
|
|
mutationFn: (provider: string) => {
|
|
return axios.get(
|
|
`/api/oauth/url/${provider}?redirect_uri=${redirectUri}`,
|
|
);
|
|
},
|
|
onError: () => {
|
|
notifications.show({
|
|
title: "Internal error",
|
|
message: "Failed to get OAuth URL",
|
|
color: "red",
|
|
});
|
|
},
|
|
onSuccess: (data) => {
|
|
notifications.show({
|
|
title: "Redirecting",
|
|
message: "Redirecting to your OAuth provider",
|
|
color: "blue",
|
|
});
|
|
setTimeout(() => {
|
|
window.location.href = data.data.url;
|
|
}, 500);
|
|
},
|
|
});
|
|
|
|
const handleSubmit = (values: FormValues) => {
|
|
loginMutation.mutate(values);
|
|
};
|
|
|
|
return (
|
|
<Layout>
|
|
<Title ta="center">Tinyauth</Title>
|
|
<Paper shadow="md" p="xl" mt={30} radius="md" withBorder>
|
|
{oauthProviders.length > 0 && (
|
|
<>
|
|
<Text size="lg" fw={500} ta="center">
|
|
Welcome back, login with
|
|
</Text>
|
|
<Grid mb="md" mt="md" align="center" justify="center">
|
|
{oauthProviders.includes("google") && (
|
|
<Grid.Col span="content">
|
|
<Button
|
|
radius="xl"
|
|
leftSection={
|
|
<GoogleIcon style={{ width: 14, height: 14 }} />
|
|
}
|
|
variant="default"
|
|
onClick={() => loginOAuthMutation.mutate("google")}
|
|
loading={loginOAuthMutation.isLoading}
|
|
>
|
|
Google
|
|
</Button>
|
|
</Grid.Col>
|
|
)}
|
|
{oauthProviders.includes("github") && (
|
|
<Grid.Col span="content">
|
|
<Button
|
|
radius="xl"
|
|
leftSection={
|
|
<GithubIcon style={{ width: 14, height: 14 }} />
|
|
}
|
|
variant="default"
|
|
onClick={() => loginOAuthMutation.mutate("github")}
|
|
loading={loginOAuthMutation.isLoading}
|
|
>
|
|
Github
|
|
</Button>
|
|
</Grid.Col>
|
|
)}
|
|
{oauthProviders.includes("tailscale") && (
|
|
<Grid.Col span="content">
|
|
<Button
|
|
radius="xl"
|
|
leftSection={
|
|
<TailscaleIcon style={{ width: 14, height: 14 }} />
|
|
}
|
|
variant="default"
|
|
onClick={() => loginOAuthMutation.mutate("tailscale")}
|
|
loading={loginOAuthMutation.isLoading}
|
|
>
|
|
Tailscale
|
|
</Button>
|
|
</Grid.Col>
|
|
)}
|
|
{oauthProviders.includes("generic") && (
|
|
<Grid.Col span="content">
|
|
<Button
|
|
radius="xl"
|
|
leftSection={
|
|
<OAuthIcon style={{ width: 14, height: 14 }} />
|
|
}
|
|
variant="default"
|
|
onClick={() => loginOAuthMutation.mutate("generic")}
|
|
loading={loginOAuthMutation.isLoading}
|
|
>
|
|
Generic
|
|
</Button>
|
|
</Grid.Col>
|
|
)}
|
|
</Grid>
|
|
{configuredProviders.includes("username") && (
|
|
<Divider
|
|
label="Or continue with password"
|
|
labelPosition="center"
|
|
my="lg"
|
|
/>
|
|
)}
|
|
</>
|
|
)}
|
|
{configuredProviders.includes("username") && (
|
|
<form onSubmit={form.onSubmit(handleSubmit)}>
|
|
<TextInput
|
|
label="Username"
|
|
placeholder="user@example.com"
|
|
required
|
|
disabled={loginMutation.isLoading}
|
|
key={form.key("username")}
|
|
{...form.getInputProps("username")}
|
|
/>
|
|
<PasswordInput
|
|
label="Password"
|
|
placeholder="password"
|
|
required
|
|
mt="md"
|
|
disabled={loginMutation.isLoading}
|
|
key={form.key("password")}
|
|
{...form.getInputProps("password")}
|
|
/>
|
|
<Button
|
|
fullWidth
|
|
mt="xl"
|
|
type="submit"
|
|
loading={loginMutation.isLoading}
|
|
>
|
|
Login
|
|
</Button>
|
|
</form>
|
|
)}
|
|
</Paper>
|
|
</Layout>
|
|
);
|
|
};
|