Skip to content

Commit 563ed48

Browse files
committed
feat: 添加本地开发者策略,即没有.env的情况
1 parent b245705 commit 563ed48

File tree

6 files changed

+132
-31
lines changed

6 files changed

+132
-31
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Login Flow Overview
2+
3+
这个目录承载站点的 NextAuth 路由处理。下面是登录流程与依赖的简要说明:
4+
5+
- **身份验证框架**:使用 [NextAuth.js 5 (Auth.js)](https://authjs.dev/) 作为认证核心,通过 `app/auth.ts` 暴露的 `handlers` 响应 GET/POST 请求。
6+
- **OAuth provider**:接入 GitHub OAuth(`next-auth/providers/github`),读取用户的 `id/name/email/avatar` 并持久化到本地用户表。
7+
- **数据库适配器**:优先使用 [@auth/neon-adapter](https://authjs.dev/reference/adapter/neon) 将用户、账户、会话数据写入 Neon Postgres。若运行环境缺少 `DATABASE_URL`,系统会回退到 JWT 会话策略,不再访问数据库,方便协作者在无 Neon 凭据的情况下开发。
8+
- **会话策略**:有 Neon 配置时启用数据库会话(`strategy: "database"`),否则改用默认的 JWT 签名。
9+
- **必要环境变量**`AUTH_SECRET`/`NEXTAUTH_SECRET` 用于签名;`AUTH_GITHUB_ID``AUTH_GITHUB_SECRET` 用于 GitHub OAuth;`DATABASE_URL` 控制 Neon 连接(可选)。开发环境缺少这些变量时会给出控制台警告并使用安全兜底逻辑,以保证本地能跑通。
10+
11+
### 本地无 `.env` 的执行策略
12+
13+
- 如果 `.env` 没有配置,只要 GitHub 登录仍在,NextAuth 会使用内置的开发密钥和 JWT 会话继续工作,登录流程不会报错。
14+
- Neon 数据库适配器会被自动禁用,此时用户信息只保存在 cookie/JWT 中,不会写入 `users / sessions` 表;适合纯前端协作者快速启动项目。
15+
- 控制台会输出显式警告,提示缺少密钥或数据库连接,确保真正部署前补齐配置。
16+
17+
如需扩展更多 provider 或调整会话策略,可直接修改 `auth.config.ts``auth.ts`

app/components/Header.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@ import { Button } from "../../components/ui/button";
33
import { MessageCircle } from "lucide-react";
44
import { Github as GithubIcon } from "./icons/Github";
55
import { SignInButton } from "./SignInButton";
6+
import { auth } from "@/auth";
7+
import { UserMenu } from "./UserMenu";
68

7-
export function Header() {
9+
export async function Header() {
10+
const session = await auth();
11+
const user = session?.user;
812
return (
913
<header className="fixed top-0 w-full z-50 bg-background/80 backdrop-blur-lg border-b border-border">
1014
<div className="container mx-auto px-6 h-16 flex items-center justify-between">
@@ -63,7 +67,7 @@ export function Header() {
6367
</a>
6468
</Button>
6569
<ThemeToggle />
66-
<SignInButton />
70+
{user ? <UserMenu user={user} /> : <SignInButton />}
6771
</div>
6872
</div>
6973
</header>

app/components/UserMenu.tsx

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { signOut } from "@/auth";
2+
import {
3+
Avatar,
4+
AvatarFallback,
5+
AvatarImage,
6+
} from "@/app/components/ui/avatar";
7+
8+
interface UserMenuProps {
9+
user: {
10+
name?: string | null;
11+
email?: string | null;
12+
image?: string | null;
13+
};
14+
}
15+
16+
export function UserMenu({ user }: UserMenuProps) {
17+
const initials = user.name?.[0] ?? user.email?.[0] ?? "?";
18+
19+
return (
20+
<details className="relative inline-block text-left">
21+
<summary
22+
className="flex cursor-pointer list-none items-center rounded-full border border-border bg-background p-0.5 transition hover:border-primary/60 [&::-webkit-details-marker]:hidden"
23+
aria-label="Account menu"
24+
>
25+
<Avatar className="size-9">
26+
{user.image ? (
27+
<AvatarImage src={user.image} alt={user.name ?? "User avatar"} />
28+
) : (
29+
<AvatarFallback>{initials}</AvatarFallback>
30+
)}
31+
</Avatar>
32+
</summary>
33+
34+
<div className="absolute right-0 mt-2 w-60 overflow-hidden rounded-md border border-border bg-popover shadow-lg">
35+
<div className="border-b border-border bg-muted/40 px-4 py-3">
36+
<p className="text-sm font-medium text-foreground">
37+
{user.name ?? "Signed in"}
38+
</p>
39+
{user.email ? (
40+
<p className="text-xs text-muted-foreground" title={user.email}>
41+
{user.email}
42+
</p>
43+
) : null}
44+
</div>
45+
46+
<form
47+
action={async () => {
48+
"use server";
49+
await signOut();
50+
}}
51+
>
52+
<button
53+
type="submit"
54+
className="w-full px-4 py-2 text-left text-sm text-foreground transition hover:bg-muted"
55+
>
56+
Sign out
57+
</button>
58+
</form>
59+
</div>
60+
</details>
61+
);
62+
}

app/page.tsx

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,10 @@ import { Hero } from "./components/Hero";
33
import { Features } from "./components/Features";
44
import { Community } from "./components/Community";
55
import { Footer } from "./components/Footer";
6-
import { neon } from "@neondatabase/serverless";
7-
8-
export function Page() {
9-
async function create(formData: FormData) {
10-
"use server";
11-
// Connect to the Neon database
12-
const sql = neon(`${process.env.DATABASE_URL}`);
13-
const comment = formData.get("comment");
14-
// Insert the comment from the form into the Postgres database
15-
await sql`INSERT INTO comments (comment) VALUES (${comment})`;
16-
}
17-
18-
return (
19-
<form action={create} className="flex flex-col gap-2 mt-72">
20-
<input type="text" placeholder="write a comment" name="comment" />
21-
<button type="submit">Submit</button>
22-
</form>
23-
);
24-
}
256

267
export default function DocsIndex() {
278
return (
289
<>
29-
<Page />
3010
<Header />
3111
<Hero />
3212
<Features />

auth.config.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,26 @@
11
import type { NextAuthConfig } from "next-auth";
22
import GitHub from "next-auth/providers/github";
33

4+
// 在本地开发环境允许没有 .env 的协作者运行站点,因此先尝试读取两个常见的密钥变量,缺失时再使用内置的开发兜底值。
5+
const envSecret = process.env.AUTH_SECRET ?? process.env.NEXTAUTH_SECRET;
6+
const secret =
7+
envSecret ??
8+
(process.env.NODE_ENV !== "production"
9+
? "__involutionhell_dev_secret__"
10+
: undefined);
11+
12+
if (!envSecret && process.env.NODE_ENV !== "production") {
13+
console.warn(
14+
"[auth] AUTH_SECRET missing – using development fallback secret",
15+
);
16+
}
17+
18+
if (!secret) {
19+
throw new Error("[auth] AUTH_SECRET is required in production environments");
20+
}
21+
422
export const authConfig = {
23+
secret,
524
pages: {
625
signIn: "/login",
726
},

auth.ts

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
1-
import NextAuth, { CredentialsSignin } from "next-auth";
1+
import NextAuth from "next-auth";
22
import { authConfig } from "./auth.config";
33
import GitHub from "next-auth/providers/github";
44
import { Pool } from "@neondatabase/serverless";
55
import NeonAdapter from "@auth/neon-adapter";
66

7-
class InvalidLoginError extends CredentialsSignin {
8-
code = "Invalid identifier or password";
9-
}
7+
type NeonAdapterPool = Parameters<typeof NeonAdapter>[0];
108

119
export const { handlers, auth, signIn, signOut } = NextAuth(() => {
12-
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
10+
// Neon 连接只在有数据库配置时启用;本地协作者若没有 `.env`,将回退为纯 JWT 会话,避免直接抛错阻塞开发。
11+
const databaseUrl = process.env.DATABASE_URL;
12+
const adapter = databaseUrl
13+
? NeonAdapter(
14+
new Pool({
15+
connectionString: databaseUrl,
16+
}) as unknown as NeonAdapterPool,
17+
)
18+
: undefined;
19+
20+
if (!databaseUrl) {
21+
console.warn("[auth] DATABASE_URL missing – running without Neon adapter");
22+
}
23+
1324
return {
1425
...authConfig,
15-
adapter: NeonAdapter(pool),
1626
providers: [
1727
GitHub({
1828
profile(profile) {
@@ -25,8 +35,17 @@ export const { handlers, auth, signIn, signOut } = NextAuth(() => {
2535
},
2636
}),
2737
],
38+
...(adapter
39+
? {
40+
adapter,
41+
session: {
42+
strategy: "database" as const,
43+
},
44+
}
45+
: {
46+
session: {
47+
strategy: "jwt" as const,
48+
},
49+
}),
2850
};
29-
session: {
30-
strategy: "database";
31-
}
3251
});

0 commit comments

Comments
 (0)