Vectrum
Sign In
69 lines · 3.2 KB · TypeScript
"use server";

import { redirect } from "next/navigation";
import { completeSignIn, endSession, safeNext, signIn } from "@/lib/auth";
import { requestPasswordReset, resetPassword, sendVerificationEmailLater } from "@/lib/emails";
import { MAIL_ENABLED } from "@/lib/mail";
import { SIGNUPS_ENABLED } from "@/lib/signups";
import { createUser, UserError, validatePassword } from "@/lib/users";
import type { FormState } from "./types";

export async function signupAction(_prev: FormState, formData: FormData): Promise<FormState> {
  if (!SIGNUPS_ENABLED) return { error: "Sign-ups are closed." };
  const password = String(formData.get("password") ?? "");
  try {
    const user = await createUser({
      username: String(formData.get("username") ?? ""),
      email: String(formData.get("email") ?? ""),
      password,
      name: String(formData.get("name") ?? ""),
    });
    sendVerificationEmailLater(user);
    await signIn(user.email, password);
  } catch (e) {
    if (e instanceof UserError) return { error: e.message };
    throw e;
  }
  redirect(safeNext(formData.get("next")));
}

export async function loginAction(_prev: FormState, formData: FormData): Promise<FormState> {
  const result = await signIn(String(formData.get("login") ?? ""), String(formData.get("password") ?? ""));
  if (!result) return { error: "Incorrect username or password." };
  const next = safeNext(formData.get("next"));
  if (result.needsCode) redirect(next === "/" ? "/login/two-factor" : `/login/two-factor?next=${encodeURIComponent(next)}`);
  redirect(next);
}

export async function twoFactorLoginAction(_prev: FormState, formData: FormData): Promise<FormState> {
  const next = safeNext(formData.get("next"));
  const result = await completeSignIn(String(formData.get("code") ?? ""));
  if (result === "invalid") return { error: "That code didn't match. Try the newest code from your app." };
  if (result === "locked") return { error: "Too many wrong codes. Wait 15 minutes, then try again." };
  if (result === "expired") redirect(`/login?expired=1${next !== "/" ? `&next=${encodeURIComponent(next)}` : ""}`);
  redirect(next);
}

export async function forgotPasswordAction(_prev: FormState, formData: FormData): Promise<FormState> {
  if (!MAIL_ENABLED) return { error: "This server can't send email. Ask the person who runs it to reset your password." };
  const login = String(formData.get("login") ?? "").trim();
  if (!login) return { error: "Enter your username or email address." };
  await requestPasswordReset(login);
  return { success: "If that's an account here, we've sent a link to choose a new password to its email address. It works for one hour." };
}

export async function resetPasswordAction(_prev: FormState, formData: FormData): Promise<FormState> {
  const password = String(formData.get("password") ?? "");
  if (password !== formData.get("confirm")) return { error: "The passwords don't match." };
  const problem = validatePassword(password);
  if (problem) return { error: problem };
  if (!(await resetPassword(String(formData.get("token") ?? ""), password))) {
    return { error: "This link has expired or was already used. Ask for a new one." };
  }
  redirect("/login?reset=1");
}

export async function logoutAction() {
  await endSession();
  redirect("/");
}