Your Name 53ddb7e585
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
Initial commit - clean repository
2025-12-17 20:15:18 +08:00

166 lines
4.7 KiB
TypeScript

import { compare } from "bcrypt-ts";
import NextAuth, { type DefaultSession } from "next-auth";
import type { DefaultJWT } from "next-auth/jwt";
import Credentials from "next-auth/providers/credentials";
import { DUMMY_PASSWORD } from "@/lib/constants";
import { createGuestUser, getUser } from "@/lib/db/queries";
import { authConfig } from "./auth.config";
import { db } from "@/lib/db";
import { loginToken } from "@/lib/db/schema";
import { eq, and, gt } from "drizzle-orm";
export type UserType = "guest" | "regular" | "admin";
declare module "next-auth" {
interface Session extends DefaultSession {
user: {
id: string;
type: UserType;
name?: string | null;
} & DefaultSession["user"];
}
// biome-ignore lint/nursery/useConsistentTypeDefinitions: "Required"
interface User {
id?: string;
email?: string | null;
name?: string | null;
type: UserType;
}
}
declare module "next-auth/jwt" {
interface JWT extends DefaultJWT {
id: string;
type: UserType;
name?: string | null;
}
}
export const {
handlers: { GET, POST },
auth,
signIn,
signOut,
} = NextAuth({
...authConfig,
trustHost: true,
providers: [
Credentials({
credentials: {},
async authorize(credentials: any) {
console.log("[Auth] Authorize called with credentials:", Object.keys(credentials));
// 1. DingTalk Token 登录逻辑
if (credentials.token) {
const { token } = credentials;
console.log("[Auth] DingTalk logic triggered with token:", token ? token.substring(0, 8) + "..." : "null");
const now = new Date();
const [validToken] = await db
.select()
.from(loginToken)
.where(and(eq(loginToken.token, token), gt(loginToken.expiresAt, now)))
.limit(1);
console.log("[Auth] Token lookup result:", validToken ? {
email: validToken.email,
expiresAt: validToken.expiresAt,
isExpired: validToken.expiresAt <= now
} : "not found");
if (!validToken) {
console.log("[Auth] Token not found or expired, returning null");
return null;
}
const users = await getUser(validToken.email);
console.log("[Auth] User lookup for", validToken.email, ":", users.length, "users found");
if (users.length === 0) {
console.log("[Auth] No user found for email, returning null");
return null;
}
const user = users[0];
console.log("[Auth] User found:", { id: user.id, email: user.email, isActive: user.isActive, role: user.role });
// 检查用户是否被激活
if (!user.isActive) {
console.log("[Auth] User is not active, returning null");
return null;
}
await db.delete(loginToken).where(eq(loginToken.id, validToken.id));
console.log("[Auth] Login token deleted");
const userType: UserType = (user.role as UserType) || "regular";
const returnUser = { id: user.id, email: user.email, name: user.name, type: userType };
console.log("[Auth] Returning user object:", returnUser);
return returnUser;
}
// 2. 邮箱密码登录逻辑
const { email, password } = credentials;
const users = await getUser(email);
if (users.length === 0) {
await compare(password, DUMMY_PASSWORD);
return null;
}
const [user] = users;
if (!user.password) {
await compare(password, DUMMY_PASSWORD);
return null;
}
const passwordsMatch = await compare(password, user.password);
if (!passwordsMatch) {
return null;
}
// 检查用户是否被激活
if (!user.isActive) {
return null;
}
// 从数据库读取用户角色,默认为 regular
const userType: UserType = (user.role as UserType) || "regular";
return { id: user.id, email: user.email, name: user.name, type: userType };
},
}),
Credentials({
id: "guest",
credentials: {},
async authorize() {
const [guestUser] = await createGuestUser();
return { ...guestUser, type: "guest" };
},
}),
],
callbacks: {
jwt({ token, user }) {
if (user) {
token.id = user.id as string;
token.type = user.type;
token.name = user.name;
}
return token;
},
session({ session, token }) {
if (session.user) {
session.user.id = token.id;
session.user.type = token.type;
session.user.name = token.name as string;
}
return session;
},
},
});