Fix-auth-hashing-update-worklog
This commit is contained in:
@@ -1,5 +1,20 @@
|
||||
# Work Log
|
||||
|
||||
## 2026-02-24
|
||||
|
||||
### 1. Credentials 登录与密码安全修复
|
||||
* **密码哈希规范化**:
|
||||
* 注册时统一生成标准 bcrypt 前缀(`$2a/$2b/$2y`),确保数据库内哈希格式一致。
|
||||
* 登录时对非标准前缀(如 `$10$...`)自动补全为 `$2b$...` 再校验,避免格式不一致导致登录失败。
|
||||
* **登录一致性修复**:
|
||||
* 登录校验不再对密码做 `trim()`,避免前后空格导致的 bcrypt 比对失败。
|
||||
* **调试排障(已回收)**:
|
||||
* 临时加入安全日志定位失败原因(不输出明文),定位后已移除。
|
||||
|
||||
### 2. 验证
|
||||
* `npm run typecheck`:通过
|
||||
* `npm run lint`:存在既有告警(未新增)
|
||||
|
||||
## 2026-01-15
|
||||
|
||||
### 1. Schedule Module Optimization
|
||||
|
||||
15
package-lock.json
generated
15
package-lock.json
generated
@@ -36,6 +36,7 @@
|
||||
"@tiptap/pm": "^3.15.3",
|
||||
"@tiptap/react": "^3.15.3",
|
||||
"@tiptap/starter-kit": "^3.15.3",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"drizzle-orm": "^0.45.1",
|
||||
@@ -63,6 +64,7 @@
|
||||
"@faker-js/faker": "^10.1.0",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
@@ -5397,6 +5399,13 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/bcryptjs": {
|
||||
"version": "2.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz",
|
||||
"integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-array": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
|
||||
@@ -6461,6 +6470,12 @@
|
||||
"baseline-browser-mapping": "dist/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/bcryptjs": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
|
||||
"integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bignumber.js": {
|
||||
"version": "9.3.1",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
"@tiptap/starter-kit": "^3.15.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"drizzle-orm": "^0.45.1",
|
||||
"lucide-react": "^0.562.0",
|
||||
"mysql2": "^3.16.0",
|
||||
@@ -68,6 +69,7 @@
|
||||
"@faker-js/faker": "^10.1.0",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Metadata } from "next"
|
||||
import { hash } from "bcryptjs"
|
||||
import { createId } from "@paralleldrive/cuid2"
|
||||
import { eq } from "drizzle-orm"
|
||||
|
||||
@@ -10,6 +11,12 @@ export const metadata: Metadata = {
|
||||
description: "Create an account",
|
||||
}
|
||||
|
||||
const normalizeBcryptHash = (value: string) => {
|
||||
if (value.startsWith("$2")) return value
|
||||
if (value.startsWith("$")) return `$2b${value}`
|
||||
return `$2b$${value}`
|
||||
}
|
||||
|
||||
export default function RegisterPage() {
|
||||
async function registerAction(formData: FormData): Promise<ActionState> {
|
||||
"use server"
|
||||
@@ -37,11 +44,12 @@ export default function RegisterPage() {
|
||||
})
|
||||
if (existing) return { success: false, message: "该邮箱已注册" }
|
||||
|
||||
const hashedPassword = normalizeBcryptHash(await hash(password, 10))
|
||||
await db.insert(users).values({
|
||||
id: createId(),
|
||||
name: name.length ? name : null,
|
||||
email,
|
||||
password,
|
||||
password: hashedPassword,
|
||||
role: "student",
|
||||
})
|
||||
|
||||
|
||||
31
src/auth.ts
31
src/auth.ts
@@ -1,6 +1,19 @@
|
||||
import { compare, hash } from "bcryptjs"
|
||||
import NextAuth from "next-auth"
|
||||
import Credentials from "next-auth/providers/credentials"
|
||||
|
||||
const normalizeRole = (value: unknown) => {
|
||||
const role = String(value ?? "").trim().toLowerCase()
|
||||
if (role === "admin" || role === "student" || role === "teacher" || role === "parent") return role
|
||||
return "student"
|
||||
}
|
||||
|
||||
const normalizeBcryptHash = (value: string) => {
|
||||
if (value.startsWith("$2")) return value
|
||||
if (value.startsWith("$")) return `$2b${value}`
|
||||
return `$2b$${value}`
|
||||
}
|
||||
|
||||
export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
trustHost: true,
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
@@ -29,17 +42,17 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
if (!user) return null
|
||||
|
||||
const storedPassword = user.password ?? null
|
||||
if (storedPassword) {
|
||||
if (storedPassword !== password) return null
|
||||
} else if (process.env.NODE_ENV === "production") {
|
||||
return null
|
||||
}
|
||||
if (!storedPassword) return null
|
||||
const normalizedPassword = normalizeBcryptHash(storedPassword)
|
||||
if (!normalizedPassword.startsWith("$2")) return null
|
||||
const ok = await compare(password, normalizedPassword)
|
||||
if (!ok) return null
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
name: user.name ?? undefined,
|
||||
email: user.email,
|
||||
role: (user.role ?? "student") as string,
|
||||
role: normalizeRole(user.role),
|
||||
}
|
||||
},
|
||||
}),
|
||||
@@ -48,7 +61,7 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
jwt: async ({ token, user }) => {
|
||||
if (user) {
|
||||
token.id = (user as { id: string }).id
|
||||
token.role = (user as { role?: string }).role ?? "student"
|
||||
token.role = normalizeRole((user as { role?: string }).role)
|
||||
token.name = (user as { name?: string }).name
|
||||
}
|
||||
|
||||
@@ -66,7 +79,7 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
})
|
||||
|
||||
if (fresh) {
|
||||
token.role = fresh.role ?? token.role ?? "student"
|
||||
token.role = normalizeRole(fresh.role ?? token.role)
|
||||
token.name = fresh.name ?? token.name
|
||||
}
|
||||
}
|
||||
@@ -76,7 +89,7 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
session: async ({ session, token }) => {
|
||||
if (session.user) {
|
||||
session.user.id = String(token.id ?? "")
|
||||
session.user.role = String(token.role ?? "student")
|
||||
session.user.role = normalizeRole(token.role)
|
||||
if (typeof token.name === "string") {
|
||||
session.user.name = token.name
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user