feat(storage): MinIO object storage, Neon Auth, Supabase removal
Deploy to route.crispygoat.com / deploy (push) Failing after 3m1s
Deploy to route.crispygoat.com / deploy (push) Failing after 3m1s
- Add MinIO/S3-compatible storage client (src/lib/storage.ts) with uploadObject, deleteObject, presigned URL helpers, and BUCKETS constant - Wire product images, brand logos, and water log photos to MinIO via the new storage client - Migrate forgot-password to Neon Auth (remove Supabase /auth/v1/recover call) - Migrate send-scheduled cron to direct Postgres + Resend (remove Supabase Edge Function proxy) - Add logoUrl to email types (OrderReceipt, Welcome, PasswordReset) and pass brand_settings.logo_url from all call sites - Update email templates to use dynamic logoUrl instead of hardcoded Supabase bucket URLs - Remove hardcoded Supabase URLs from TuxedoVideoHero, TuxedoAboutPage, TimeTrackingFieldClient; use brand_settings props + local public/ fallback - Download brand logos (3) and tuxedo-hero.mp4 (36MB) from Supabase bucket to public/ for local development - Add MinIO env vars to .env.example (endpoint, access key, secret, buckets) - Fix TimeTrackingFieldClient to destructure logoUrl and brandAccent props - Fix admin/users.ts logoUrl type (null → undefined for optional string) - Remove stale sb- cookie from wholesale-auth - Migrate tuxedo/about page to remove supabase import and use pool query for wholesale_settings lookup
This commit is contained in:
+31
-77
@@ -1,89 +1,43 @@
|
||||
import "server-only";
|
||||
|
||||
/**
|
||||
* Auth.js (NextAuth v5) — server-side configuration.
|
||||
* Neon Auth (Better Auth) — server-side configuration.
|
||||
*
|
||||
* This file is Node-only. It is imported by:
|
||||
* - `src/app/api/auth/[...nextauth]/route.ts` (the OAuth + credentials handlers)
|
||||
* - Server actions that call `signIn` / `signOut`
|
||||
* - `src/lib/admin-permissions.ts` (reads `auth()` for the current user)
|
||||
* - `src/app/api/auth/[...nextauth]/route.ts` (the API handler)
|
||||
* - Server actions that call signIn / signOut
|
||||
* - `src/lib/admin-permissions.ts` (reads getSession() for the current user)
|
||||
*
|
||||
* The middleware imports a separate, edge-safe instance built from
|
||||
* `src/auth.config.ts`. Both instances share the same JWT cookie, so the
|
||||
* middleware can read sessions minted here.
|
||||
* `src/auth.config.ts`. Both instances share the same session cookie,
|
||||
* so the middleware can read sessions minted here.
|
||||
*
|
||||
* Providers:
|
||||
* - Google OAuth — active when AUTH_GOOGLE_ID + AUTH_GOOGLE_SECRET are set.
|
||||
* - Email + password (Credentials) — active in dev only; backed by the
|
||||
* `users.password_hash` column. In production, set ALLOW_DEV_LOGIN=false
|
||||
* (the default) and the provider is omitted entirely.
|
||||
*
|
||||
* For local dev, run `npm run db:seed` to create the seeded admin user
|
||||
* (`admin@route-commerce.local` / `admin`). The `authorize` function
|
||||
* looks up the user by email, verifies the password against the stored
|
||||
* hash, and returns the real user record. No `dev_session` cookie
|
||||
* bypass; this is real Auth.js sign-in.
|
||||
* Auth providers: configured in the Neon Auth dashboard (oauth-provider).
|
||||
* Neon Auth manages its own users in the `neon_auth.user` table.
|
||||
* Our `admin_users` table links to Neon Auth users by email.
|
||||
*/
|
||||
|
||||
import NextAuth from "next-auth";
|
||||
import Credentials from "next-auth/providers/credentials";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { authConfig, isDevLoginEnabled } from "@/auth.config";
|
||||
import { withDb } from "@/db/client";
|
||||
import { users } from "@/db/schema";
|
||||
import { verifyPassword } from "@/lib/passwords";
|
||||
import { createNeonAuth } from "@neondatabase/auth/next/server";
|
||||
import { getNeonAuthConfig } from "@/auth.config";
|
||||
|
||||
function buildCredentialsProvider() {
|
||||
return Credentials({
|
||||
id: "credentials",
|
||||
name: "Email + password",
|
||||
credentials: {
|
||||
email: { label: "Email", type: "email" },
|
||||
password: { label: "Password", type: "password" },
|
||||
},
|
||||
/**
|
||||
* Returns the user on success, or `null` on any failure. Auth.js
|
||||
* never throws from `authorize` — a throw is treated as a 500.
|
||||
*/
|
||||
async authorize(creds) {
|
||||
if (!isDevLoginEnabled()) return null;
|
||||
const email = String(creds?.email ?? "").trim().toLowerCase();
|
||||
const password = String(creds?.password ?? "");
|
||||
if (!email || !password) return null;
|
||||
|
||||
try {
|
||||
// The `users` table is global (not tenant-scoped), so we use
|
||||
// `withDb` rather than `withTenant` — no GUC to set.
|
||||
const u = await withDb(async (db) => {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(users)
|
||||
.where(eq(users.email, email))
|
||||
.limit(1);
|
||||
return rows[0] ?? null;
|
||||
});
|
||||
if (!u || !u.passwordHash) return null;
|
||||
if (!verifyPassword(password, u.passwordHash)) return null;
|
||||
return {
|
||||
id: u.id,
|
||||
name: u.name ?? undefined,
|
||||
email: u.email,
|
||||
};
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("[auth] credentials authorize failed:", err);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const providers = [
|
||||
...authConfig.providers,
|
||||
...(isDevLoginEnabled() ? [buildCredentialsProvider()] : []),
|
||||
];
|
||||
|
||||
export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
...authConfig,
|
||||
providers,
|
||||
const auth = createNeonAuth({
|
||||
baseUrl: getNeonAuthConfig().baseUrl,
|
||||
cookies: { secret: getNeonAuthConfig().cookieSecret },
|
||||
});
|
||||
|
||||
export const { getSession, signIn, signOut, resetPassword, requestPasswordReset } = auth;
|
||||
export const { listUsers, createUser, setRole, setUserPassword, updateUser } = auth.admin;
|
||||
|
||||
/**
|
||||
* Re-exported for the API route handler compatibility.
|
||||
* `GET` and `POST` from `auth.handler()` handle all Neon Auth endpoints.
|
||||
*/
|
||||
const authInstance = auth.handler();
|
||||
export const { GET: handlersGET, POST: handlersPOST } = authInstance;
|
||||
|
||||
/**
|
||||
* Re-exported for middleware compatibility (edge runtime can't use Node-only auth).
|
||||
* The middleware uses `neonAuthMiddleware` from `@neondatabase/auth/next/server` directly.
|
||||
* This named export exists for call sites that import `auth` from this module.
|
||||
*/
|
||||
export { auth };
|
||||
|
||||
Reference in New Issue
Block a user