Initial import: property management SaaS + security hardening + admin dashboard
Property Management Network — Next.js 16 (App Router), Better Auth, Drizzle ORM over PostgreSQL, Stripe, OpenAI, Resend. Includes: - Security hardening: access-control/IDOR fixes, TLS-by-default DB layer, constant-time cron auth, strict security headers, atomic AI quota gating, HTML/email output encoding, demo-backdoor disabled in production. - Superadmin dashboard at /admin (overview/MRR, server-paginated users with ban/impersonate/plan/delete, billing, platform activity + admin audit log, AI usage, system health) via the Better Auth admin plugin. - Seed/migration utility scripts under scripts/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
"use server"
|
||||
|
||||
import { redirect } from "next/navigation"
|
||||
import { headers } from "next/headers"
|
||||
import { APIError } from "better-auth/api"
|
||||
import { auth } from "@/lib/auth"
|
||||
|
||||
const APP_URL = process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000"
|
||||
|
||||
export async function signUp(formData: FormData) {
|
||||
const email = formData.get("email") as string
|
||||
const password = formData.get("password") as string
|
||||
const fullName = formData.get("full_name") as string
|
||||
|
||||
try {
|
||||
await auth.api.signUpEmail({
|
||||
body: { email, password, name: fullName },
|
||||
headers: await headers(),
|
||||
})
|
||||
} catch (e) {
|
||||
const msg = e instanceof APIError ? e.message : "Sign up failed"
|
||||
redirect(`/signup?error=${encodeURIComponent(msg)}`)
|
||||
}
|
||||
|
||||
redirect("/dashboard")
|
||||
}
|
||||
|
||||
export async function signIn(formData: FormData) {
|
||||
const email = formData.get("email") as string
|
||||
const password = formData.get("password") as string
|
||||
|
||||
try {
|
||||
await auth.api.signInEmail({
|
||||
body: { email, password },
|
||||
headers: await headers(),
|
||||
})
|
||||
} catch (e) {
|
||||
const msg = e instanceof APIError ? e.message : "Invalid email or password"
|
||||
redirect(`/login?error=${encodeURIComponent(msg)}`)
|
||||
}
|
||||
|
||||
redirect("/dashboard")
|
||||
}
|
||||
|
||||
export async function signInWithGoogle() {
|
||||
let url: string | undefined
|
||||
try {
|
||||
const res = await auth.api.signInSocial({
|
||||
body: { provider: "google", callbackURL: "/dashboard" },
|
||||
headers: await headers(),
|
||||
})
|
||||
url = res?.url ?? undefined
|
||||
} catch (e) {
|
||||
const msg = e instanceof APIError ? e.message : "Google sign-in failed"
|
||||
redirect(`/login?error=${encodeURIComponent(msg)}`)
|
||||
}
|
||||
|
||||
if (url) redirect(url)
|
||||
redirect("/login?error=google_failed")
|
||||
}
|
||||
|
||||
export async function resetPassword(formData: FormData) {
|
||||
const email = formData.get("email") as string
|
||||
|
||||
try {
|
||||
await auth.api.requestPasswordReset({
|
||||
body: { email, redirectTo: `${APP_URL}/update-password` },
|
||||
headers: await headers(),
|
||||
})
|
||||
} catch {
|
||||
// Always report success so we don't reveal whether an account exists.
|
||||
}
|
||||
|
||||
redirect("/forgot-password?success=email-sent")
|
||||
}
|
||||
|
||||
export async function signOut() {
|
||||
try {
|
||||
await auth.api.signOut({ headers: await headers() })
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
redirect("/login")
|
||||
}
|
||||
|
||||
export async function updatePassword(formData: FormData) {
|
||||
const password = formData.get("password") as string
|
||||
const token = formData.get("token") as string
|
||||
|
||||
if (!token) {
|
||||
redirect(`/update-password?error=${encodeURIComponent("Reset link is invalid or expired.")}`)
|
||||
}
|
||||
|
||||
try {
|
||||
await auth.api.resetPassword({
|
||||
body: { newPassword: password, token },
|
||||
headers: await headers(),
|
||||
})
|
||||
} catch (e) {
|
||||
const msg = e instanceof APIError ? e.message : "Could not update password"
|
||||
redirect(`/update-password?error=${encodeURIComponent(msg)}&token=${encodeURIComponent(token)}`)
|
||||
}
|
||||
|
||||
redirect("/login?success=password-updated")
|
||||
}
|
||||
Reference in New Issue
Block a user