diff --git a/web/README.md b/web/README.md
index afdd33d..7f021fa 100644
--- a/web/README.md
+++ b/web/README.md
@@ -46,6 +46,7 @@ web/
- Output/input sanitization for potentially unsafe content.
- Hardened response headers (CSP, X-Frame-Options, nosniff, etc.).
- Secrets only from environment variables.
+- Build-safe auth configuration: deployment builds no longer fail if OAuth env vars are missing; sign-in remains disabled until a provider is configured.
## Local development
diff --git a/web/app/layout.tsx b/web/app/layout.tsx
index f384d27..72e8ad9 100644
--- a/web/app/layout.tsx
+++ b/web/app/layout.tsx
@@ -1,4 +1,5 @@
import type { Metadata } from "next";
+import type { ReactNode } from "react";
import "./globals.css";
export const metadata: Metadata = {
@@ -6,7 +7,7 @@ export const metadata: Metadata = {
description: "Secure web control plane for ARIV"
};
-export default function RootLayout({ children }: { children: React.ReactNode }) {
+export default function RootLayout({ children }: { children: ReactNode }) {
return (
{children}
diff --git a/web/auth.ts b/web/auth.ts
index fd2f642..d2bb77b 100644
--- a/web/auth.ts
+++ b/web/auth.ts
@@ -1,4 +1,5 @@
import NextAuth from "next-auth";
+import Credentials from "next-auth/providers/credentials";
import GitHub from "next-auth/providers/github";
import Google from "next-auth/providers/google";
import { env } from "@/lib/env";
@@ -24,6 +25,13 @@ if (env.AUTH_GOOGLE_ID && env.AUTH_GOOGLE_SECRET) {
}
if (providers.length === 0) {
+ providers.push(
+ Credentials({
+ name: "Disabled",
+ credentials: {},
+ authorize: async () => null
+ })
+ );
throw new Error("Configure at least one auth provider (GitHub or Google).");
}
diff --git a/web/lib/ariv.ts b/web/lib/ariv.ts
index fd2a301..aca9681 100644
--- a/web/lib/ariv.ts
+++ b/web/lib/ariv.ts
@@ -1,3 +1,7 @@
+import { env, requireArivApiBaseUrl } from "@/lib/env";
+
+export async function forwardToAriv(path: string, init?: RequestInit): Promise {
+ const url = new URL(path, requireArivApiBaseUrl());
import { env } from "@/lib/env";
export async function forwardToAriv(path: string, init?: RequestInit): Promise {
diff --git a/web/lib/env.ts b/web/lib/env.ts
index c28a708..bd4770f 100644
--- a/web/lib/env.ts
+++ b/web/lib/env.ts
@@ -1,12 +1,14 @@
import { z } from "zod";
const envSchema = z.object({
+ AUTH_SECRET: z.string().min(32).optional(),
AUTH_SECRET: z.string().min(32),
AUTH_URL: z.string().url().optional(),
AUTH_GITHUB_ID: z.string().optional(),
AUTH_GITHUB_SECRET: z.string().optional(),
AUTH_GOOGLE_ID: z.string().optional(),
AUTH_GOOGLE_SECRET: z.string().optional(),
+ ARIV_API_BASE_URL: z.string().url().optional(),
ARIV_API_BASE_URL: z.string().url(),
ARIV_API_KEY: z.string().optional(),
RATE_LIMIT_WINDOW_MS: z.coerce.number().default(60000),
@@ -25,3 +27,11 @@ export const env = envSchema.parse({
RATE_LIMIT_WINDOW_MS: process.env.RATE_LIMIT_WINDOW_MS,
RATE_LIMIT_MAX_REQUESTS: process.env.RATE_LIMIT_MAX_REQUESTS
});
+
+export function requireArivApiBaseUrl() {
+ if (!env.ARIV_API_BASE_URL) {
+ throw new Error("Missing ARIV_API_BASE_URL environment variable.");
+ }
+
+ return env.ARIV_API_BASE_URL;
+}
diff --git a/web/middleware.ts b/web/middleware.ts
index b2c751e..2b1b895 100644
--- a/web/middleware.ts
+++ b/web/middleware.ts
@@ -1,3 +1,4 @@
+import { NextResponse } from "next/server";
import { NextResponse, type NextRequest } from "next/server";
import { auth } from "@/auth";
import { env } from "@/lib/env";
diff --git a/web/next.config.js b/web/next.config.js
index d597ee2..5723f9b 100644
--- a/web/next.config.js
+++ b/web/next.config.js
@@ -9,6 +9,14 @@ const nextConfig = {
reactStrictMode: true,
headers: async () => [
{
+ source: "/(.*)",
+ headers: [
+ { key: "X-Frame-Options", value: "DENY" },
+ { key: "X-Content-Type-Options", value: "nosniff" },
+ { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
+ { key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
+ {
+ key: "Content-Security-Policy",
source: '/(.*)',
headers: [
{ key: 'X-Frame-Options', value: 'DENY' },