added basic auth

This commit is contained in:
2025-08-17 14:13:58 +01:00
parent 1bf8e1a055
commit 4cafa1fb0b
3 changed files with 68 additions and 1 deletions

View File

@@ -1,3 +1,12 @@
# Get a free API key at https://dev.pokemontcg.io/ # Get a free API key at https://dev.pokemontcg.io/
# Then copy this file to .env.local and paste your key below # Then copy this file to .env.local and paste your key(s) below
# Client-side API key (exposed to browser)
NEXT_PUBLIC_POKEMON_TCG_API_KEY= NEXT_PUBLIC_POKEMON_TCG_API_KEY=
# Optional: Server-side API key (preferred). If set, server uses this and does not rely on public key.
POKEMON_TCG_API_KEY=
# Optional: Enable very simple single-user Basic Auth (set both to enable)
BASIC_AUTH_USER=
BASIC_AUTH_PASS=

View File

@@ -13,6 +13,9 @@ services:
# Forward any API keys if needed # Forward any API keys if needed
- NEXT_PUBLIC_POKEMON_TCG_API_KEY=${NEXT_PUBLIC_POKEMON_TCG_API_KEY} - NEXT_PUBLIC_POKEMON_TCG_API_KEY=${NEXT_PUBLIC_POKEMON_TCG_API_KEY}
- POKEMON_TCG_API_KEY=${POKEMON_TCG_API_KEY} - POKEMON_TCG_API_KEY=${POKEMON_TCG_API_KEY}
# Optional: enable very simple single-user Basic Auth
- BASIC_AUTH_USER=${BASIC_AUTH_USER}
- BASIC_AUTH_PASS=${BASIC_AUTH_PASS}
ports: ports:
# Select host port via APP_PORT env var; default 3000 # Select host port via APP_PORT env var; default 3000
- "${APP_PORT:-3000}:3000" - "${APP_PORT:-3000}:3000"

55
src/middleware.ts Normal file
View File

@@ -0,0 +1,55 @@
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
// Very simple single-user HTTP Basic Auth.
// Set BASIC_AUTH_USER and BASIC_AUTH_PASS in the environment to enable.
// If these are not set, auth is disabled and all requests pass through.
export function middleware(req: NextRequest) {
const user = process.env.BASIC_AUTH_USER;
const pass = process.env.BASIC_AUTH_PASS;
if (!user || !pass) {
// Auth disabled
return NextResponse.next();
}
const header = req.headers.get('authorization') || '';
const prefix = 'Basic ';
if (!header.startsWith(prefix)) {
return unauthorized('Authentication required');
}
try {
const decoded = atob(header.slice(prefix.length));
const idx = decoded.indexOf(':');
const u = decoded.slice(0, idx);
const p = decoded.slice(idx + 1);
if (u === user && p === pass) {
return NextResponse.next();
}
} catch {
// fallthrough
}
return unauthorized('Invalid credentials');
}
function unauthorized(message: string) {
return new NextResponse(message, {
status: 401,
headers: {
'WWW-Authenticate': 'Basic realm="Restricted", charset="UTF-8"',
'Content-Type': 'text/plain; charset=utf-8',
},
});
}
// Apply to all routes (including API). Static assets will also be behind auth; browsers will reuse credentials.
export const config = {
matcher: [
// Exclude Next static assets and image optimizer
'/((?!_next/static|_next/image).*)',
],
};