From 192e38cd673401d4d72f306167e3d563cbf430eb Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 8 Apr 2025 16:54:38 -0400 Subject: [PATCH] feat: allow registration disabling --- app/account/login/page.tsx | 15 +- app/account/signup/layout.tsx | 17 ++ app/api/users/create/route.ts | 307 ++++++++++++++++-------------- components/pages/main/Hero.tsx | 19 +- components/pages/main/Pricing.tsx | 38 +++- 5 files changed, 231 insertions(+), 165 deletions(-) create mode 100644 app/account/signup/layout.tsx diff --git a/app/account/login/page.tsx b/app/account/login/page.tsx index ee4b575..f779f8a 100644 --- a/app/account/login/page.tsx +++ b/app/account/login/page.tsx @@ -31,12 +31,19 @@ export default async function Login() { Sign in with Authentik - - + + ) : ( + - + )} diff --git a/app/account/signup/layout.tsx b/app/account/signup/layout.tsx new file mode 100644 index 0000000..b0177de --- /dev/null +++ b/app/account/signup/layout.tsx @@ -0,0 +1,17 @@ +import React from "react" + +export default function SignupLayout({ + children, +}: { + children: React.ReactNode +}) { + if (process.env.SIGNUP_ENABLED === "false") { + return
Signup is disabled
+ } else if (process.env.SIGNUP_ENABLED === "true") { + return ( +
{children}
+ ) + } else { + return
Invalid SIGNUP_ENABLED environment variable
+ } +} \ No newline at end of file diff --git a/app/api/users/create/route.ts b/app/api/users/create/route.ts index beebf2f..e02096d 100644 --- a/app/api/users/create/route.ts +++ b/app/api/users/create/route.ts @@ -7,166 +7,179 @@ import { validateToken } from "@/lib/utils" // (2) Migrate a p0ntus mail account to a LibreCloud account (creates/migrates Authentik/Email) async function createEmail(email: string, password: string, migrate: boolean) { - try { - if (!process.env.MAIL_CONNECT_API_URL) { - console.error("[!] Missing MAIL_CONNECT_API_URL environment variable") - return { success: false, message: "Server configuration error" } - } - - const response = await axios.post(`${process.env.MAIL_CONNECT_API_URL}/accounts/add`, { - email, - password, - migrate, - }) - - const responseData = response.data - if (responseData.success) { - return response.data - } else if (responseData.error) { - console.error("[!] Email creation failed:", responseData.error) - return { success: false, message: responseData.error } - } else { - console.error("[!] Email creation failed with unknown error") + // Signup status check + if (process.env.SIGNUP_ENABLED === "false") { + return { success: false, message: "Signups are disabled" } + } else if (process.env.SIGNUP_ENABLED === "true") { + try { + if (!process.env.MAIL_CONNECT_API_URL) { + console.error("[!] Missing MAIL_CONNECT_API_URL environment variable") + return { success: false, message: "Server configuration error" } + } else { + const response = await axios.post(`${process.env.MAIL_CONNECT_API_URL}/accounts/add`, { + email, + password, + migrate, + }) + + const responseData = response.data + if (responseData.success) { + return response.data + } else if (responseData.error) { + console.error("[!] Email creation failed:", responseData.error) + return { success: false, message: responseData.error } + } else { + console.error("[!] Email creation failed with unknown error") + return { success: false, message: "Failed to create email account" } + } + } + } catch (error) { + console.error("[!] Email creation error:", error) + if (axios.isAxiosError(error)) { + return { + success: false, + message: error.response?.data?.error || "Failed to connect to email service", + } + } return { success: false, message: "Failed to create email account" } } - } catch (error) { - console.error("[!] Email creation error:", error) - if (axios.isAxiosError(error)) { - return { - success: false, - message: error.response?.data?.error || "Failed to connect to email service", - } - } - return { success: false, message: "Failed to create email account" } + } else { + return { success: false, message: "Account signup is not configured in your environment variables!" } } } export async function POST(request: Request) { - try { - const body = await request.json() - const { name, email, password, migrate, token } = body + if (process.env.SIGNUP_ENABLED === "true") { + try { + const body = await request.json() + const { name, email, password, migrate, token } = body - // Validate fields - if (!name || !email || !password) { - return NextResponse.json({ success: false, message: "The form you submitted is incomplete" }, { status: 400 }) - } - - const tokenValidation = await validateToken(token) - if (!tokenValidation.success) { - console.error("Turnstile validation failed:", tokenValidation.error) - return NextResponse.json({ success: false, message: "Robot check failed, try refreshing" }, { status: 400 }) - } - - if (!process.env.AUTHENTIK_API_URL || !process.env.AUTHENTIK_API_KEY) { - console.error("Missing Authentik environment variables") - return NextResponse.json({ success: false, message: "Server configuration error" }, { status: 500 }) - } - - // Create Authentik user - const genUser = email.split("@")[0] - const userData = { - username: genUser, - name, - is_active: true, - groups: [ - "b2c38bad-1d15-4ffd-b6d4-d95370a092ca" // this represents the "Users" group in Authentik - ], - email, - type: "internal", - } - - console.log("[i] Creating user in Authentik:", { username: genUser, email }) - - const response = await axios.request({ - method: "post", - maxBodyLength: Infinity, - url: `${process.env.AUTHENTIK_API_URL}/core/users/`, - headers: { - "Content-Type": "application/json", - Accept: "application/json", - Authorization: `Bearer ${process.env.AUTHENTIK_API_KEY}`, - }, - data: JSON.stringify(userData), - validateStatus: () => true, // capture response even for error status codes - }) - - if (response.data?.detail) { - console.error("[!] Authentik user creation issue:", response.data.detail) - } - - if (response.status !== 201) { - if (response.data.username && response.data.username[0] === "This field must be unique.") { - return NextResponse.json({ success: false, message: "Username already exists" }, { status: 409 }) + // Validate fields + if (!name || !email || !password) { + return NextResponse.json({ success: false, message: "The form you submitted is incomplete" }, { status: 400 }) } - console.error("Failed to create user in Authentik:", response.status, response.data) - return NextResponse.json({ success: false, message: "Failed to create user account" }, { status: 500 }) - } - - // User created successfully, now set password - const userID = response.data.pk - const updData = { - password, - } - - console.log("[i] Setting password for user:", userID) - - const updCfg = await axios.request({ - method: "post", - maxBodyLength: Number.POSITIVE_INFINITY, - url: `${process.env.AUTHENTIK_API_URL}/core/users/${userID}/set_password/`, - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${process.env.AUTHENTIK_API_KEY}`, - }, - data: updData, - validateStatus: () => true, // capture response even for error status codes - }) - - if (updCfg.data?.detail) { - console.error("[!] Password setting issue:", updCfg.data.detail) - } - - if (updCfg.status === 204) { - // account created successfully, now create email - console.log("[i] Creating email account for:", email) - const emailRes = await createEmail(email, password, migrate) - - if (emailRes.success) { - console.log("[S] Account creation successful for:", email) - } else { - console.error("[!] Email creation failed for:", email, emailRes.message) + const tokenValidation = await validateToken(token) + if (!tokenValidation.success) { + console.error("Turnstile validation failed:", tokenValidation.error) + return NextResponse.json({ success: false, message: "Robot check failed, try refreshing" }, { status: 400 }) } - return NextResponse.json(emailRes, { - status: emailRes.success ? 200 : 500, + if (!process.env.AUTHENTIK_API_URL || !process.env.AUTHENTIK_API_KEY) { + console.error("Missing Authentik environment variables") + return NextResponse.json({ success: false, message: "Server configuration error" }, { status: 500 }) + } + + // Create Authentik user + const genUser = email.split("@")[0] + const userData = { + username: genUser, + name, + is_active: true, + groups: [ + "b2c38bad-1d15-4ffd-b6d4-d95370a092ca" // this represents the "Users" group in Authentik + ], + email, + type: "internal", + } + + console.log("[i] Creating user in Authentik:", { username: genUser, email }) + + const response = await axios.request({ + method: "post", + maxBodyLength: Infinity, + url: `${process.env.AUTHENTIK_API_URL}/core/users/`, + headers: { + "Content-Type": "application/json", + Accept: "application/json", + Authorization: `Bearer ${process.env.AUTHENTIK_API_KEY}`, + }, + data: JSON.stringify(userData), + validateStatus: () => true, // capture response even for error status codes }) - } else if (updCfg.status === 400) { - console.error("[!] Failed to set password:", updCfg.data) - return NextResponse.json({ success: false, message: "Invalid password format" }, { status: 400 }) - } else { - console.error("[!] Unknown error setting password:", updCfg.status, updCfg.data) - return NextResponse.json({ success: false, message: "Failed to complete account setup" }, { status: 500 }) - } - } catch (error: unknown) { - if (axios.isAxiosError(error)) { - if (error.response?.data?.detail) { - console.error("[!] Request error with detail:", error.response.data.detail) - return NextResponse.json({ success: false, message: "Server error - Failed to create user" }, { status: 500 }) - } else if (error.response?.data?.error) { - console.error("[!] Request error (passed from Authentik):", error.response.data.error) - return NextResponse.json({ success: false, message: error.response.data.error }, { status: 500 }) - } else if (error.code === "ECONNREFUSED" || error.code === "ENOTFOUND") { - console.error("[!] Connection error:", error.message) - return NextResponse.json( - { success: false, message: "Failed to connect to authentication service" }, - { status: 503 }, - ) - } - } - console.error("[!] Unhandled error while creating user:", error) - return NextResponse.json({ success: false, message: "An unexpected error occurred" }, { status: 500 }) + if (response.data?.detail) { + console.error("[!] Authentik user creation issue:", response.data.detail) + } + + if (response.status !== 201) { + if (response.data.username && response.data.username[0] === "This field must be unique.") { + return NextResponse.json({ success: false, message: "Username already exists" }, { status: 409 }) + } + + console.error("Failed to create user in Authentik:", response.status, response.data) + return NextResponse.json({ success: false, message: "Failed to create user account" }, { status: 500 }) + } + + // User created successfully, now set password + const userID = response.data.pk + const updData = { + password, + } + + console.log("[i] Setting password for user:", userID) + + const updCfg = await axios.request({ + method: "post", + maxBodyLength: Number.POSITIVE_INFINITY, + url: `${process.env.AUTHENTIK_API_URL}/core/users/${userID}/set_password/`, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${process.env.AUTHENTIK_API_KEY}`, + }, + data: updData, + validateStatus: () => true, // capture response even for error status codes + }) + + if (updCfg.data?.detail) { + console.error("[!] Password setting issue:", updCfg.data.detail) + } + + if (updCfg.status === 204) { + // account created successfully, now create email + console.log("[i] Creating email account for:", email) + const emailRes = await createEmail(email, password, migrate) + + if (emailRes.success) { + console.log("[S] Account creation successful for:", email) + } else { + console.error("[!] Email creation failed for:", email, emailRes.message) + } + + return NextResponse.json(emailRes, { + status: emailRes.success ? 200 : 500, + }) + } else if (updCfg.status === 400) { + console.error("[!] Failed to set password:", updCfg.data) + return NextResponse.json({ success: false, message: "Invalid password format" }, { status: 400 }) + } else { + console.error("[!] Unknown error setting password:", updCfg.status, updCfg.data) + return NextResponse.json({ success: false, message: "Failed to complete account setup" }, { status: 500 }) + } + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + if (error.response?.data?.detail) { + console.error("[!] Request error with detail:", error.response.data.detail) + return NextResponse.json({ success: false, message: "Server error - Failed to create user" }, { status: 500 }) + } else if (error.response?.data?.error) { + console.error("[!] Request error (passed from Authentik):", error.response.data.error) + return NextResponse.json({ success: false, message: error.response.data.error }, { status: 500 }) + } else if (error.code === "ECONNREFUSED" || error.code === "ENOTFOUND") { + console.error("[!] Connection error:", error.message) + return NextResponse.json( + { success: false, message: "Failed to connect to authentication service" }, + { status: 503 }, + ) + } + } + + console.error("[!] Unhandled error while creating user:", error) + return NextResponse.json({ success: false, message: "An unexpected error occurred" }, { status: 500 }) + } + } else if (process.env.SIGNUP_ENABLED === "false") { + return NextResponse.json({ success: false, message: "Signups are disabled" }, { status: 403 }) + } else { + return NextResponse.json({ success: false, message: "Account signup is not configured in your environment variables!" }, { status: 500 }) } } diff --git a/components/pages/main/Hero.tsx b/components/pages/main/Hero.tsx index 6bfa437..7254af0 100644 --- a/components/pages/main/Hero.tsx +++ b/components/pages/main/Hero.tsx @@ -1,7 +1,7 @@ "use client" import { Button } from "@/components/ui/button" -import { ArrowRight } from "lucide-react" +import { ArrowRight, XCircle } from "lucide-react" import { ReactTyped } from "react-typed" import Link from "next/link"; @@ -23,12 +23,19 @@ const Hero = () => {

- - + + ) : ( + - + )}
diff --git a/components/pages/main/Pricing.tsx b/components/pages/main/Pricing.tsx index 73e16c5..9d26bb3 100644 --- a/components/pages/main/Pricing.tsx +++ b/components/pages/main/Pricing.tsx @@ -1,7 +1,7 @@ "use client" import type React from "react" -import { Check, ChevronRight, Clock } from "lucide-react" +import { Check, ChevronRight, Clock, XCircle } from "lucide-react" import { Separator } from "@/components/ui/separator" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" @@ -145,6 +145,21 @@ const PricingCard: React.FC = ({ )}
+ {title !== "Everything" ? ( + + Starting at + + ) : ( + + Always + + )} = ({ {isComingSoon ? ( ) : buttonText ? ( - - + + ) : ( + - + ) ) : null}
@@ -215,7 +237,7 @@ export default function Pricing(): React.ReactElement { features={features.everything} badge="Most Popular" buttonText="Get Started" - buttonIcon={} + buttonIcon={} />