| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| import { NextRequest, NextResponse } from "next/server"; |
|
|
| export const config = { |
| matcher: ["/((?!_next/static|_next/image|_next/data|favicon.ico).*)"], |
| }; |
|
|
| const REALM = 'Basic realm="Etiya BSS Atelier", charset="UTF-8"'; |
|
|
| export function middleware(req: NextRequest) { |
| const password = process.env.UI_PASSWORD; |
| const username = process.env.UI_USERNAME || "etiya"; |
| |
| if (!password) return NextResponse.next(); |
|
|
| const auth = req.headers.get("authorization"); |
| if (!auth?.startsWith("Basic ")) { |
| return unauthorised(); |
| } |
|
|
| let user = ""; |
| let pass = ""; |
| try { |
| const decoded = atob(auth.slice("Basic ".length)); |
| const colon = decoded.indexOf(":"); |
| if (colon >= 0) { |
| user = decoded.slice(0, colon); |
| pass = decoded.slice(colon + 1); |
| } |
| } catch { |
| return unauthorised(); |
| } |
|
|
| if ( |
| !constantTimeEqual(user, username) || |
| !constantTimeEqual(pass, password) |
| ) { |
| return unauthorised(); |
| } |
| return NextResponse.next(); |
| } |
|
|
| function unauthorised() { |
| return new NextResponse("Authentication required.", { |
| status: 401, |
| headers: { |
| "WWW-Authenticate": REALM, |
| "Content-Type": "text/plain; charset=utf-8", |
| |
| "Cache-Control": "no-store", |
| }, |
| }); |
| } |
|
|
| function constantTimeEqual(a: string, b: string): boolean { |
| if (a.length !== b.length) return false; |
| let diff = 0; |
| for (let i = 0; i < a.length; i++) { |
| diff |= a.charCodeAt(i) ^ b.charCodeAt(i); |
| } |
| return diff === 0; |
| } |
|
|