refactor: replace signIn callback with signIn event, adjust getUserByEmail in adapter to check provider (#1223)

* refactor: replace signIn callback with signIn event, adjust getUserByEmail in adapter to check provider

* test: adjusting tests for adapter and events

* docs: add comments for unknown auth provider

* fix: missing dayjs import
This commit is contained in:
Meier Lukas
2024-10-07 21:13:15 +02:00
committed by GitHub
parent 4d51e3b344
commit eb21628ee4
19 changed files with 521 additions and 423 deletions

View File

@@ -12,8 +12,7 @@ import type { useForm } from "@homarr/form";
import { useZodForm } from "@homarr/form";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import { useScopedI18n } from "@homarr/translation/client";
import type { z } from "@homarr/validation";
import { validation } from "@homarr/validation";
import { validation, z } from "@homarr/validation";
interface LoginFormProps {
providers: string[];
@@ -22,15 +21,17 @@ interface LoginFormProps {
callbackUrl: string;
}
const extendedValidation = validation.user.signIn.extend({ provider: z.enum(["credentials", "ldap"]) });
export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, callbackUrl }: LoginFormProps) => {
const t = useScopedI18n("user");
const router = useRouter();
const [isPending, setIsPending] = useState(false);
const form = useZodForm(validation.user.signIn, {
const form = useZodForm(extendedValidation, {
initialValues: {
name: "",
password: "",
credentialType: "basic",
provider: "credentials",
},
});
@@ -95,14 +96,14 @@ export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, c
<Stack gap="lg">
{credentialInputsVisible && (
<>
<form onSubmit={form.onSubmit((credentials) => void signInAsync("credentials", credentials))}>
<form onSubmit={form.onSubmit((credentials) => void signInAsync(credentials.provider, credentials))}>
<Stack gap="lg">
<TextInput label={t("field.username.label")} {...form.getInputProps("name")} />
<PasswordInput label={t("field.password.label")} {...form.getInputProps("password")} />
{providers.includes("credentials") && (
<Stack gap="sm">
<SubmitButton isPending={isPending} form={form} credentialType="basic">
<SubmitButton isPending={isPending} form={form} provider="credentials">
{t("action.login.label")}
</SubmitButton>
<PasswordForgottenCollapse username={form.values.name} />
@@ -110,7 +111,7 @@ export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, c
)}
{providers.includes("ldap") && (
<SubmitButton isPending={isPending} form={form} credentialType="ldap">
<SubmitButton isPending={isPending} form={form} provider="ldap">
{t("action.login.labelWith", { provider: "LDAP" })}
</SubmitButton>
)}
@@ -133,18 +134,18 @@ export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, c
interface SubmitButtonProps {
isPending: boolean;
form: ReturnType<typeof useForm<FormType, (values: FormType) => FormType>>;
credentialType: "basic" | "ldap";
provider: "credentials" | "ldap";
}
const SubmitButton = ({ isPending, form, credentialType, children }: PropsWithChildren<SubmitButtonProps>) => {
const isCurrentProviderActive = form.getValues().credentialType === credentialType;
const SubmitButton = ({ isPending, form, provider, children }: PropsWithChildren<SubmitButtonProps>) => {
const isCurrentProviderActive = form.getValues().provider === provider;
return (
<Button
type="submit"
name={credentialType}
name={provider}
fullWidth
onClick={() => form.setFieldValue("credentialType", credentialType)}
onClick={() => form.setFieldValue("provider", provider)}
loading={isPending && isCurrentProviderActive}
disabled={isPending && !isCurrentProviderActive}
>
@@ -181,4 +182,4 @@ const PasswordForgottenCollapse = ({ username }: PasswordForgottenCollapseProps)
);
};
type FormType = z.infer<typeof validation.user.signIn>;
type FormType = z.infer<typeof extendedValidation>;

View File

@@ -1,17 +1,37 @@
import { NextRequest } from "next/server";
import { createHandlers } from "@homarr/auth";
import type { SupportedAuthProvider } from "@homarr/definitions";
import { logger } from "@homarr/log";
export const GET = async (req: NextRequest) => {
return await createHandlers(isCredentialsRequest(req)).handlers.GET(reqWithTrustedOrigin(req));
return await createHandlers(extractProvider(req)).handlers.GET(reqWithTrustedOrigin(req));
};
export const POST = async (req: NextRequest) => {
return await createHandlers(isCredentialsRequest(req)).handlers.POST(reqWithTrustedOrigin(req));
return await createHandlers(extractProvider(req)).handlers.POST(reqWithTrustedOrigin(req));
};
const isCredentialsRequest = (req: NextRequest) => {
return req.url.includes("credentials") && req.method === "POST";
/**
* This method extracts the used provider from the url and allows us to override the getUserByEmail method in the adapter.
* @param req request containing the url
* @returns the provider or "unknown" if the provider could not be extracted
*/
const extractProvider = (req: NextRequest): SupportedAuthProvider | "unknown" => {
const url = new URL(req.url);
if (url.pathname.includes("oidc")) {
return "oidc";
}
if (url.pathname.includes("credentials")) {
return "credentials";
}
if (url.pathname.includes("ldap")) {
return "ldap";
}
return "unknown";
};
/**