mirror of
https://github.com/beilunyang/moemail.git
synced 2026-08-27 11:00:04 +08:00
feat: Implement username/password authentication and registration features
This commit is contained in:
140
app/lib/auth.ts
140
app/lib/auth.ts
@@ -2,10 +2,14 @@ import NextAuth from "next-auth"
|
||||
import GitHub from "next-auth/providers/github"
|
||||
import { DrizzleAdapter } from "@auth/drizzle-adapter"
|
||||
import { createDb, Db } from "./db"
|
||||
import { accounts, sessions, users, roles, userRoles } from "./schema"
|
||||
import { accounts, users, roles, userRoles } from "./schema"
|
||||
import { eq } from "drizzle-orm"
|
||||
import { getRequestContext } from "@cloudflare/next-on-pages"
|
||||
import { Permission, hasPermission, ROLES, Role } from "./permissions"
|
||||
import CredentialsProvider from "next-auth/providers/credentials"
|
||||
import { hashPassword, comparePassword } from "@/lib/utils"
|
||||
import { authSchema } from "@/lib/validation"
|
||||
import { generateAvatarUrl } from "./avatar"
|
||||
|
||||
const ROLE_DESCRIPTIONS: Record<Role, string> = {
|
||||
[ROLES.EMPEROR]: "皇帝(网站所有者)",
|
||||
@@ -71,13 +75,53 @@ export const {
|
||||
adapter: DrizzleAdapter(createDb(), {
|
||||
usersTable: users,
|
||||
accountsTable: accounts,
|
||||
sessionsTable: sessions,
|
||||
}),
|
||||
providers: [
|
||||
GitHub({
|
||||
clientId: process.env.AUTH_GITHUB_ID,
|
||||
clientSecret: process.env.AUTH_GITHUB_SECRET,
|
||||
})
|
||||
}),
|
||||
CredentialsProvider({
|
||||
name: "Credentials",
|
||||
credentials: {
|
||||
username: { label: "用户名", type: "text", placeholder: "请输入用户名" },
|
||||
password: { label: "密码", type: "password", placeholder: "请输入密码" },
|
||||
},
|
||||
async authorize(credentials) {
|
||||
if (!credentials) {
|
||||
throw new Error("请输入用户名和密码")
|
||||
}
|
||||
|
||||
const { username, password } = credentials
|
||||
|
||||
try {
|
||||
authSchema.parse({ username, password })
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
} catch (error) {
|
||||
throw new Error("输入格式不正确")
|
||||
}
|
||||
|
||||
const db = createDb()
|
||||
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.username, username as string),
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
throw new Error("用户名或密码错误")
|
||||
}
|
||||
|
||||
const isValid = await comparePassword(password as string, user.password as string)
|
||||
if (!isValid) {
|
||||
throw new Error("用户名或密码错误")
|
||||
}
|
||||
|
||||
return {
|
||||
...user,
|
||||
password: undefined,
|
||||
}
|
||||
},
|
||||
}),
|
||||
],
|
||||
events: {
|
||||
async signIn({ user }) {
|
||||
@@ -99,37 +143,73 @@ export const {
|
||||
}
|
||||
},
|
||||
},
|
||||
pages: {
|
||||
signIn: "/",
|
||||
error: "/",
|
||||
},
|
||||
callbacks: {
|
||||
async session({ session, user }) {
|
||||
if (!session?.user) return session
|
||||
|
||||
const db = createDb()
|
||||
let userRoleRecords = await db.query.userRoles.findMany({
|
||||
where: eq(userRoles.userId, user.id),
|
||||
with: { role: true },
|
||||
})
|
||||
|
||||
if (!userRoleRecords.length) {
|
||||
const defaultRole = await getDefaultRole()
|
||||
const role = await findOrCreateRole(db, defaultRole)
|
||||
await assignRoleToUser(db, user.id, role.id)
|
||||
userRoleRecords = [{
|
||||
userId: user.id,
|
||||
roleId: role.id,
|
||||
createdAt: new Date(),
|
||||
role: role
|
||||
}]
|
||||
async jwt({ token, user }) {
|
||||
if (user) {
|
||||
token.id = user.id
|
||||
token.name = user.name || user.username
|
||||
token.username = user.username
|
||||
token.image = user.image || generateAvatarUrl(token.name as string)
|
||||
}
|
||||
return token
|
||||
},
|
||||
async session({ session, token }) {
|
||||
if (token && session.user) {
|
||||
session.user.id = token.id as string
|
||||
session.user.name = token.name as string
|
||||
session.user.username = token.username as string
|
||||
session.user.image = token.image as string
|
||||
|
||||
session.user.roles = userRoleRecords.map(ur => ({
|
||||
name: ur.role.name,
|
||||
}))
|
||||
const db = createDb()
|
||||
let userRoleRecords = await db.query.userRoles.findMany({
|
||||
where: eq(userRoles.userId, session.user.id),
|
||||
with: { role: true },
|
||||
})
|
||||
|
||||
if (!userRoleRecords.length) {
|
||||
const defaultRole = await getDefaultRole()
|
||||
const role = await findOrCreateRole(db, defaultRole)
|
||||
await assignRoleToUser(db, session.user.id, role.id)
|
||||
userRoleRecords = [{
|
||||
userId: session.user.id,
|
||||
roleId: role.id,
|
||||
createdAt: new Date(),
|
||||
role: role
|
||||
}]
|
||||
}
|
||||
|
||||
session.user.roles = userRoleRecords.map(ur => ({
|
||||
name: ur.role.name,
|
||||
}))
|
||||
}
|
||||
|
||||
return session
|
||||
},
|
||||
}
|
||||
},
|
||||
session: {
|
||||
strategy: "jwt",
|
||||
},
|
||||
}))
|
||||
|
||||
export async function register(username: string, password: string) {
|
||||
const db = createDb()
|
||||
|
||||
const existing = await db.query.users.findFirst({
|
||||
where: eq(users.username, username)
|
||||
})
|
||||
|
||||
if (existing) {
|
||||
throw new Error("用户名已存在")
|
||||
}
|
||||
|
||||
const hashedPassword = await hashPassword(password)
|
||||
|
||||
const [user] = await db.insert(users)
|
||||
.values({
|
||||
username,
|
||||
password: hashedPassword,
|
||||
})
|
||||
.returning()
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user