b119a52e08
* Adds a new route for logging in with mfa * New path for security page * Adds “Security” link to account side menu * Update the Switch component to allow label positions left and right * Optionally hide the Close button in the Dialog title bar * Installs `qrcode` react package for generating QR codes. * CopyButton component now takes children * New Security route for setting up MFA * Adds new OTP package for the chadcn InputOTP component * Adds new InputOTP chadcn component * Adds InputOTP chadcn component to the MFA login screen * InputOTP component supports variant styles * Improvements to form handling * Show a confirmation modal before you can disable MFA * Revert redirect back to the dashboard for now * Implement MFA enabling and disabling * Refactor and cleanup mfa management code * More cleanup * Handle errors in the management action * Implement mfa login flow * recovery code input should be password * Implement rate limiting on the mfa validation endpoint * Better error ux * Implement mfa emails and apply James' updates * Use latest @better-auth/utils * Improvements via CodeRabbit review --------- Co-authored-by: James Ritchie <james@trigger.dev>
67 lines
1.9 KiB
TypeScript
67 lines
1.9 KiB
TypeScript
import { redirect } from "@remix-run/node";
|
|
import { getUserById } from "~/models/user.server";
|
|
import { authenticator } from "./auth.server";
|
|
import { getImpersonationId } from "./impersonation.server";
|
|
|
|
export async function getUserId(request: Request): Promise<string | undefined> {
|
|
const impersonatedUserId = await getImpersonationId(request);
|
|
|
|
if (impersonatedUserId) return impersonatedUserId;
|
|
|
|
let authUser = await authenticator.isAuthenticated(request);
|
|
return authUser?.userId;
|
|
}
|
|
|
|
export async function getUser(request: Request) {
|
|
const userId = await getUserId(request);
|
|
if (userId === undefined) return null;
|
|
|
|
const user = await getUserById(userId);
|
|
if (user) return user;
|
|
|
|
throw await logout(request);
|
|
}
|
|
|
|
export async function requireUserId(request: Request, redirectTo?: string) {
|
|
const userId = await getUserId(request);
|
|
if (!userId) {
|
|
const url = new URL(request.url);
|
|
const searchParams = new URLSearchParams([
|
|
["redirectTo", redirectTo ?? `${url.pathname}${url.search}`],
|
|
]);
|
|
throw redirect(`/login?${searchParams}`);
|
|
}
|
|
return userId;
|
|
}
|
|
|
|
export type UserFromSession = Awaited<ReturnType<typeof requireUser>>;
|
|
|
|
export async function requireUser(request: Request) {
|
|
const userId = await requireUserId(request);
|
|
|
|
const impersonationId = await getImpersonationId(request);
|
|
const user = await getUserById(userId);
|
|
if (user) {
|
|
return {
|
|
id: user.id,
|
|
email: user.email,
|
|
name: user.name,
|
|
displayName: user.displayName,
|
|
avatarUrl: user.avatarUrl,
|
|
admin: user.admin,
|
|
createdAt: user.createdAt,
|
|
updatedAt: user.updatedAt,
|
|
dashboardPreferences: user.dashboardPreferences,
|
|
confirmedBasicDetails: user.confirmedBasicDetails,
|
|
mfaEnabledAt: user.mfaEnabledAt,
|
|
isImpersonating: !!impersonationId,
|
|
};
|
|
}
|
|
|
|
throw await logout(request);
|
|
}
|
|
|
|
export async function logout(request: Request) {
|
|
return redirect("/logout");
|
|
}
|