diff --git a/app/app/api/auth/register/route.ts b/app/app/api/auth/register/route.ts
index 5cbc79c..e9efabe 100644
--- a/app/app/api/auth/register/route.ts
+++ b/app/app/api/auth/register/route.ts
@@ -1,12 +1,13 @@
// app/api/auth/register/route.ts
import { NextResponse } from "next/server";
import { rateLimit, requestIp } from "@/lib/rate-limit";
-import {
- createDirectusUser,
- directusUserExists,
- loginDirectus,
-} from "@/lib/directus";
+const DIRECTUS = (process.env.DIRECTUS_URL || process.env.NEXT_PUBLIC_API_BASE_URL || "").replace(/\/$/, "");
+const SERVICE_TOKEN =
+process.env.DIRECTUS_SERVICE_TOKEN ||
+process.env.DIRECTUS_ADMIN_TOKEN ||
+process.env.DIRECTUS_TOKEN_ADMIN_REGISTER || "";
+const DEFAULT_ROLE = process.env.DIRECTUS_DEFAULT_ROLE || undefined;
const SECURE = process.env.NODE_ENV === "production";
function bad(message: string, status = 400) {
@@ -14,18 +15,16 @@ function bad(message: string, status = 400) {
}
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
-function isConflict(error: any): boolean {
- const code = error?.detail?.errors?.[0]?.extensions?.code;
- const message = error?.detail?.errors?.[0]?.message || error?.message || "";
- return error?.status === 409 || code === "RECORD_NOT_UNIQUE" || /unique|already exists/i.test(message);
-}
-
-function logDirectusFailure(stage: string, error: any) {
- console.error(`[auth/register] Directus ${stage} failed`, {
- status: error?.status,
- code: error?.detail?.errors?.[0]?.extensions?.code,
- message: error?.detail?.errors?.[0]?.message || error?.message,
+async function directusLogin(email: string, password: string) {
+ const r = await fetch(`${DIRECTUS}/auth/login`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
+ body: JSON.stringify({ email, password }),
+ cache: "no-store",
});
+ const j = await r.json().catch(() => ({}));
+ if (!r.ok) throw new Error(j?.errors?.[0]?.message || j?.message || `Login failed (${r.status})`);
+ return j?.data || j;
}
export async function POST(req: Request) {
@@ -33,6 +32,9 @@ export async function POST(req: Request) {
if (!rateLimit(`register:${requestIp(req)}`, 5, 60 * 60_000)) {
return bad("Too many registration attempts. Please try again later.", 429);
}
+ if (!DIRECTUS) return bad("Missing DIRECTUS_URL/NEXT_PUBLIC_API_BASE_URL", 500);
+ if (!SERVICE_TOKEN) return bad("Missing DIRECTUS_SERVICE_TOKEN / admin token", 500);
+
const body = await req.json().catch(() => ({} as any));
const email = String(body?.email ?? "").trim().toLowerCase();
const username = String(body?.username ?? "").trim();
@@ -44,35 +46,53 @@ export async function POST(req: Request) {
if (password.length < 8) return bad("Password must be at least 8 characters");
if (password !== confirm) return bad("Passwords do not match");
- try {
- if (await directusUserExists(email, username)) {
- return bad("Email or username already in use", 409);
+ // Optional pre-check to return a friendly 409 instead of a generic Directus error
+ const existsRes = await fetch(
+ `${DIRECTUS}/users?filter[_or][0][email][_eq]=${encodeURIComponent(email)}` +
+ `&filter[_or][1][username][_eq]=${encodeURIComponent(username)}` +
+ `&fields=id,email,username&limit=1`,
+ {
+ headers: {
+ Authorization: `Bearer ${SERVICE_TOKEN}`,
+ Accept: "application/json",
+ },
+ cache: "no-store",
}
- } catch (error: any) {
- logDirectusFailure("duplicate lookup", error);
- return bad("Sign-up is temporarily unavailable.", 503);
+ );
+ const existsJson = await existsRes.json().catch(() => ({}));
+ if (Array.isArray(existsJson?.data) && existsJson.data.length > 0) {
+ return bad("Email or username already in use", 409);
}
- let user: { id: string };
- try {
- // Resolves the production Users role through the Registration Bot
- // policy, then creates an active local-auth account in that role.
- user = await createDirectusUser({ email, username, password });
- } catch (error: any) {
- if (isConflict(error)) return bad("Email or username already in use", 409);
- logDirectusFailure("user creation", error);
- return bad("Sign-up is temporarily unavailable.", 503);
+ // Create user with sane defaults
+ const createPayload: any = {
+ email,
+ username,
+ password,
+ };
+ if (DEFAULT_ROLE) createPayload.role = DEFAULT_ROLE;
+
+ const createRes = await fetch(`${DIRECTUS}/users`, {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${SERVICE_TOKEN}`,
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ body: JSON.stringify(createPayload),
+ cache: "no-store",
+ });
+
+ const cj = await createRes.json().catch(() => ({}));
+ if (!createRes.ok) {
+ const msg = cj?.errors?.[0]?.message || cj?.message || `User create failed (${createRes.status})`;
+ return bad(msg, createRes.status || 500);
}
- let tokens: any;
- try {
- tokens = await loginDirectus(email, password);
- } catch (error: any) {
- logDirectusFailure("post-registration login", error);
- return bad("Your account was created, but automatic sign-in failed. Please sign in.", 502);
- }
+ // Auto-login (email-based; directus expects "email" even though it's an identifier)
+ const tokens = await directusLogin(email, password);
- const res = NextResponse.json({ ok: true, id: user.id }, { status: 201 });
+ const res = NextResponse.json({ ok: true, id: cj?.data?.id || null }, { status: 201 });
if (tokens?.access_token) {
res.cookies.set("ma_at", tokens.access_token, {
path: "/",
diff --git a/app/app/api/bgbye/process/route.ts b/app/app/api/bgbye/process/route.ts
index d41da54..7a91181 100644
--- a/app/app/api/bgbye/process/route.ts
+++ b/app/app/api/bgbye/process/route.ts
@@ -1,6 +1,7 @@
export const runtime = "nodejs";
import { NextResponse } from "next/server";
+import { getUserBearerFromRequest } from "@/lib/directus";
import { rateLimit, requestIp } from "@/lib/rate-limit";
const MAX_BODY_BYTES = 30 * 1024 * 1024;
@@ -20,6 +21,9 @@ export async function POST(req: Request) {
);
}
+ if (!getUserBearerFromRequest(req)) {
+ return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
+ }
if (!rateLimit(`bgbye:${requestIp(req)}`, 10, 60_000)) {
return NextResponse.json({ error: "Too many processing requests" }, { status: 429 });
}
diff --git a/app/app/page.tsx b/app/app/page.tsx
index cf20182..739763a 100644
--- a/app/app/page.tsx
+++ b/app/app/page.tsx
@@ -1,15 +1,49 @@
-import { Suspense } from "react";
-import TemporaryUtilityHub from "@/components/TemporaryUtilityHub";
+// app/page.tsx
+import { cookies } from "next/headers";
+import { redirect } from "next/navigation";
+import SignIn from "@/app/auth/sign-in/sign-in";
+import SignUp from "@/app/auth/sign-up/sign-up";
+import { isJwtValid } from "@/lib/jwt";
-export const metadata = {
- title: "Laser Everything Community Utilities",
- description: "Free laser calculators, public files, and image background removal tools.",
-};
+type SearchParams = { [key: string]: string | string[] | undefined };
+
+export default async function HomePage({
+ searchParams,
+}: {
+ searchParams?: SearchParams;
+}) {
+ // If already signed in with a VALID token, go straight to the app
+ const ck = await cookies();
+ const at = ck.get("ma_at")?.value;
+ if (isJwtValid(at)) redirect("/portal");
+
+ const reauth = searchParams?.reauth === "1";
-export default function HomePage() {
return (
- Loading utilities…}>
-
-
+
+ {reauth && (
+
+ Your session expired. Please sign in again.
+
+ )}
+
+
+
MakeArmy
+
+ Free to use. Manage laser rigs, settings, and projects—all in one
+ place.
+
+
+
+
+
+
+
+
+
+
This is the beta build v0.1.1 - this site is an active BETA. Some features may not be available or work as intended. If you're experiencing issues please report them here: https://forge.makearmy.io/makearmy/makearmy-app/issues
+
PRIVACY: We only use cookies strictly necessary to operate the site (e.g., your sign-in session). We do not store user data or telemetry other than what you provide and never share or sell data to third parties. Ever.
- Our database and member features are temporarily offline while we change backend
- systems. These free utilities remain available without an account.
-
-
- Directus was the system behind our member features, but it has moved to a corporate
- licensing model that we cannot afford for a free community project. We are moving the
- site to another free and open source CMS. Until that work is finished, user accounts,
- community libraries, and databases will be unavailable. We are working to bring
- everything back as soon as we can. Thank you for your patience.
-