fix: client-side auth check instead of middleware rewrite
All checks were successful
Deploy / deploy (push) Successful in 2m38s
All checks were successful
Deploy / deploy (push) Successful in 2m38s
This commit is contained in:
@@ -2,20 +2,28 @@ import { NextResponse } from 'next/server'
|
|||||||
import * as crypto from 'crypto'
|
import * as crypto from 'crypto'
|
||||||
|
|
||||||
const SECRET = process.env.APP_SECRET || 'smart-home-default-secret-change-me'
|
const SECRET = process.env.APP_SECRET || 'smart-home-default-secret-change-me'
|
||||||
|
const PIN = process.env.APP_PIN || '1234'
|
||||||
|
|
||||||
function makeToken(pin: string): string {
|
function makeToken(pin: string): string {
|
||||||
return crypto.createHmac('sha256', SECRET).update(pin).digest('hex')
|
return crypto.createHmac('sha256', SECRET).update(pin).digest('hex')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function GET(req: Request) {
|
||||||
|
const cookieHeader = req.headers.get('cookie') || ''
|
||||||
|
const match = cookieHeader.match(/auth_token=([^;]+)/)
|
||||||
|
const token = match ? match[1] : null
|
||||||
|
const expected = makeToken(PIN)
|
||||||
|
return NextResponse.json({ authenticated: token === expected })
|
||||||
|
}
|
||||||
|
|
||||||
export async function POST(req: Request) {
|
export async function POST(req: Request) {
|
||||||
const { pin } = await req.json()
|
const { pin } = await req.json()
|
||||||
const correctPin = process.env.APP_PIN || '1234'
|
|
||||||
|
|
||||||
if (pin !== correctPin) {
|
if (pin !== PIN) {
|
||||||
return NextResponse.json({ error: 'wrong_pin' }, { status: 401 })
|
return NextResponse.json({ error: 'wrong_pin' }, { status: 401 })
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = makeToken(correctPin)
|
const token = makeToken(PIN)
|
||||||
const res = NextResponse.json({ success: true })
|
const res = NextResponse.json({ success: true })
|
||||||
|
|
||||||
res.cookies.set('auth_token', token, {
|
res.cookies.set('auth_token', token, {
|
||||||
|
|||||||
26
app/page.tsx
26
app/page.tsx
@@ -1,7 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState, useEffect, useCallback, Suspense } from 'react'
|
import { useState, useEffect, useCallback } from 'react'
|
||||||
import { useSearchParams } from 'next/navigation'
|
|
||||||
import { Thermometer, Droplets, Wind, Calendar, Lock, Settings as SettingsIcon, LogOut, Delete } from 'lucide-react'
|
import { Thermometer, Droplets, Wind, Calendar, Lock, Settings as SettingsIcon, LogOut, Delete } from 'lucide-react'
|
||||||
import Sidebar from '@/components/Sidebar'
|
import Sidebar from '@/components/Sidebar'
|
||||||
import TopBar from '@/components/TopBar'
|
import TopBar from '@/components/TopBar'
|
||||||
@@ -374,9 +373,14 @@ function HomeTab({ weather, sensors }: { weather: WeatherData | null; sensors: S
|
|||||||
}
|
}
|
||||||
|
|
||||||
function HomePageInner() {
|
function HomePageInner() {
|
||||||
const searchParams = useSearchParams()
|
const [unlocked, setUnlocked] = useState<boolean | null>(null)
|
||||||
const isLocked = searchParams.get('locked') === '1'
|
|
||||||
const [unlocked, setUnlocked] = useState(!isLocked)
|
useEffect(() => {
|
||||||
|
fetch('/api/auth')
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(d => setUnlocked(d.authenticated))
|
||||||
|
.catch(() => setUnlocked(false))
|
||||||
|
}, [])
|
||||||
|
|
||||||
const [tab, setTab] = useState<Tab>('home')
|
const [tab, setTab] = useState<Tab>('home')
|
||||||
const [activeRoom, setActiveRoom] = useState('living')
|
const [activeRoom, setActiveRoom] = useState('living')
|
||||||
@@ -431,8 +435,12 @@ function HomePageInner() {
|
|||||||
window.location.reload()
|
window.location.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (unlocked === null) {
|
||||||
|
return <div style={{ display: 'flex', height: '100dvh', alignItems: 'center', justifyContent: 'center', background: 'var(--bg)' }}><div className="bg-ambient" /></div>
|
||||||
|
}
|
||||||
|
|
||||||
if (!unlocked) {
|
if (!unlocked) {
|
||||||
return <LockScreen onUnlock={() => { setUnlocked(true); window.history.replaceState({}, '', '/') }} />
|
return <LockScreen onUnlock={() => setUnlocked(true)} />
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -514,9 +522,5 @@ function HomePageInner() {
|
|||||||
|
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
return (
|
return <HomePageInner />
|
||||||
<Suspense>
|
|
||||||
<HomePageInner />
|
|
||||||
</Suspense>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,8 @@ async function hmacSha256(secret: string, message: string): Promise<string> {
|
|||||||
export async function middleware(request: NextRequest) {
|
export async function middleware(request: NextRequest) {
|
||||||
const { pathname } = request.nextUrl
|
const { pathname } = request.nextUrl
|
||||||
|
|
||||||
if (
|
// Only protect API routes (except /api/auth)
|
||||||
pathname.startsWith('/api/auth') ||
|
if (!pathname.startsWith('/api/') || pathname.startsWith('/api/auth')) {
|
||||||
pathname.startsWith('/_next') ||
|
|
||||||
pathname.startsWith('/favicon') ||
|
|
||||||
pathname === '/manifest.json'
|
|
||||||
) {
|
|
||||||
return NextResponse.next()
|
return NextResponse.next()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,17 +24,12 @@ export async function middleware(request: NextRequest) {
|
|||||||
const expectedToken = await hmacSha256(secret, pin)
|
const expectedToken = await hmacSha256(secret, pin)
|
||||||
|
|
||||||
if (token !== expectedToken) {
|
if (token !== expectedToken) {
|
||||||
if (pathname.startsWith('/api/')) {
|
|
||||||
return NextResponse.json({ error: 'unauthorized' }, { status: 401 })
|
return NextResponse.json({ error: 'unauthorized' }, { status: 401 })
|
||||||
}
|
}
|
||||||
const url = request.nextUrl.clone()
|
|
||||||
url.searchParams.set('locked', '1')
|
|
||||||
return NextResponse.rewrite(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.next()
|
return NextResponse.next()
|
||||||
}
|
}
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
matcher: ['/((?!_next/static|_next/image|favicon.ico|manifest.json).*)'],
|
matcher: ['/api/:path*'],
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user