feat: user auth, API keys, Stripe billing, and dashboard scoping

- NextAuth v5 credentials auth with registration/login pages
- API key CRUD (create, list, revoke) with secure hashing
- Stripe checkout, webhooks, and customer portal integration
- Rate limiting per subscription tier
- All dashboard API endpoints scoped to authenticated user
- Prisma schema: User, Account, Session, ApiKey, plus Stripe fields
- Auth middleware protecting dashboard and API routes

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-Claude)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Vectry
2026-02-10 15:37:49 +00:00
parent 07cf717c15
commit 61268f870f
33 changed files with 2247 additions and 57 deletions

View File

@@ -0,0 +1,50 @@
import NextAuth from "next-auth";
import { NextResponse } from "next/server";
import authConfig from "./auth.config";
const { auth } = NextAuth(authConfig);
const publicPaths = [
"/",
"/docs",
"/api/auth",
"/api/traces",
"/api/health",
];
function isPublicPath(pathname: string): boolean {
return publicPaths.some(
(p) => pathname === p || pathname.startsWith(`${p}/`)
);
}
export default auth((req) => {
const { pathname } = req.nextUrl;
const isLoggedIn = !!req.auth;
if (isPublicPath(pathname)) {
if (isLoggedIn && (pathname === "/login" || pathname === "/register")) {
return NextResponse.redirect(new URL("/dashboard", req.nextUrl.origin));
}
return NextResponse.next();
}
if (pathname === "/login" || pathname === "/register") {
if (isLoggedIn) {
return NextResponse.redirect(new URL("/dashboard", req.nextUrl.origin));
}
return NextResponse.next();
}
if (pathname.startsWith("/dashboard") && !isLoggedIn) {
const loginUrl = new URL("/login", req.nextUrl.origin);
loginUrl.searchParams.set("callbackUrl", pathname);
return NextResponse.redirect(loginUrl);
}
return NextResponse.next();
});
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico|og-image.png).*)"],
};