Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion web/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import type { Metadata } from "next";
import type { ReactNode } from "react";
import "./globals.css";

export const metadata: Metadata = {
title: "ARIV Console",
description: "Secure web control plane for ARIV"
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en" className="dark">
<body>{children}</body>
Expand Down
8 changes: 8 additions & 0 deletions web/auth.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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).");
}

Expand Down
4 changes: 4 additions & 0 deletions web/lib/ariv.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { env, requireArivApiBaseUrl } from "@/lib/env";

export async function forwardToAriv<T>(path: string, init?: RequestInit): Promise<T> {
const url = new URL(path, requireArivApiBaseUrl());
import { env } from "@/lib/env";

export async function forwardToAriv<T>(path: string, init?: RequestInit): Promise<T> {
Expand Down
10 changes: 10 additions & 0 deletions web/lib/env.ts
Original file line number Diff line number Diff line change
@@ -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),
Expand All @@ -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;
}
1 change: 1 addition & 0 deletions web/middleware.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
8 changes: 8 additions & 0 deletions web/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down