Skip to content

Commit 05d4a1a

Browse files
committed
feat: add authentication middleware and login page
1 parent 6b71bd4 commit 05d4a1a

File tree

4 files changed

+149
-0
lines changed

4 files changed

+149
-0
lines changed

app/api/auth/route.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { NextResponse } from 'next/server';
2+
3+
export async function POST(request: Request) {
4+
try {
5+
const { password } = await request.json();
6+
const correctPassword = process.env.AUTH_PASSWORD;
7+
8+
if (!correctPassword) {
9+
return NextResponse.json(
10+
{ error: 'Authentication not configured' },
11+
{ status: 500 }
12+
);
13+
}
14+
15+
if (password === correctPassword) {
16+
// Create the response
17+
const response = NextResponse.json({ success: true });
18+
19+
// Set the authentication cookie
20+
response.cookies.set('authenticated', 'true', {
21+
httpOnly: true,
22+
secure: process.env.NODE_ENV === 'production',
23+
sameSite: 'lax',
24+
maxAge: 7 * 24 * 60 * 60 // 7 days
25+
});
26+
27+
return response;
28+
}
29+
30+
return NextResponse.json(
31+
{ error: 'Invalid password' },
32+
{ status: 401 }
33+
);
34+
} catch (error) {
35+
return NextResponse.json(
36+
{ error: 'Internal server error' },
37+
{ status: 500 }
38+
);
39+
}
40+
}

app/auth/page.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { AuthPage } from '@/components/auth-page';
2+
3+
export default function Page() {
4+
return <AuthPage />;
5+
}

components/auth-page.tsx

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
'use client';
2+
3+
import { useRouter } from 'next/navigation';
4+
import { useState } from 'react';
5+
import { Button } from './ui/button';
6+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
7+
import { Input } from './ui/input';
8+
9+
export function AuthPage() {
10+
const [password, setPassword] = useState('');
11+
const [error, setError] = useState('');
12+
const [isLoading, setIsLoading] = useState(false);
13+
const router = useRouter();
14+
15+
const handleSubmit = async (e: React.FormEvent) => {
16+
e.preventDefault();
17+
setIsLoading(true);
18+
setError('');
19+
20+
try {
21+
const response = await fetch('/api/auth', {
22+
method: 'POST',
23+
headers: {
24+
'Content-Type': 'application/json',
25+
},
26+
body: JSON.stringify({ password }),
27+
});
28+
29+
if (response.ok) {
30+
router.refresh(); // 刷新页面以更新认证状态
31+
router.push('/');
32+
} else {
33+
const data = await response.json();
34+
setError(data.error || '密码错误');
35+
}
36+
} catch (err) {
37+
setError('验证失败,请重试');
38+
} finally {
39+
setIsLoading(false);
40+
}
41+
};
42+
43+
return (
44+
<div className="flex h-screen items-center justify-center bg-background">
45+
<Card className="w-[350px]">
46+
<CardHeader>
47+
<CardTitle>访问验证</CardTitle>
48+
<CardDescription>请输入访问密码</CardDescription>
49+
</CardHeader>
50+
<CardContent>
51+
<form onSubmit={handleSubmit} className="space-y-4">
52+
<Input
53+
type="password"
54+
placeholder="请输入密码"
55+
value={password}
56+
onChange={(e) => setPassword(e.target.value)}
57+
disabled={isLoading}
58+
/>
59+
{error && <p className="text-sm text-red-500">{error}</p>}
60+
<Button type="submit" className="w-full" disabled={isLoading}>
61+
{isLoading ? '验证中...' : '确认'}
62+
</Button>
63+
</form>
64+
</CardContent>
65+
</Card>
66+
</div>
67+
);
68+
}

middleware.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { NextRequest } from 'next/server';
2+
import { NextResponse } from 'next/server';
3+
4+
export function middleware(request: NextRequest) {
5+
// 检查是否需要鉴权
6+
const authPassword = process.env.AUTH_PASSWORD;
7+
if (!authPassword) {
8+
return NextResponse.next();
9+
}
10+
11+
// 排除不需要鉴权的路径
12+
if (
13+
request.nextUrl.pathname.startsWith('/api/auth') ||
14+
request.nextUrl.pathname.startsWith('/_next') ||
15+
request.nextUrl.pathname.startsWith('/favicon.ico')
16+
) {
17+
return NextResponse.next();
18+
}
19+
20+
// 检查认证状态
21+
const isAuthenticated = request.cookies.get('authenticated');
22+
if (!isAuthenticated && request.nextUrl.pathname !== '/auth') {
23+
return NextResponse.redirect(new URL('/auth', request.url));
24+
}
25+
26+
// 如果已认证且访问 /auth 页面,重定向到首页
27+
if (isAuthenticated && request.nextUrl.pathname === '/auth') {
28+
return NextResponse.redirect(new URL('/', request.url));
29+
}
30+
31+
return NextResponse.next();
32+
}
33+
34+
export const config = {
35+
matcher: ['/((?!api/auth|_next/static|_next/image|favicon.ico).*)']
36+
};

0 commit comments

Comments
 (0)