From 714ad21b7460b0f92abd805abb84c2e38c3db12b Mon Sep 17 00:00:00 2001 From: sijinhui Date: Thu, 18 Apr 2024 10:04:10 +0800 Subject: [PATCH 1/2] merge --- app/api/admin/users/[[...path]]/route.ts | 17 ++- app/app/(auth)/login/user-login-button.tsx | 39 ++++++- lib/auth.ts | 119 +++++++++------------ 3 files changed, 101 insertions(+), 74 deletions(-) diff --git a/app/api/admin/users/[[...path]]/route.ts b/app/api/admin/users/[[...path]]/route.ts index 9f8e8268a..4a53239f0 100644 --- a/app/api/admin/users/[[...path]]/route.ts +++ b/app/api/admin/users/[[...path]]/route.ts @@ -51,7 +51,22 @@ async function handle( }, }); const count = result.length; - return NextResponse.json({ count: count, results: result }); + return NextResponse.json({ + count: count, + results: result.map((item) => { + return { + id: item.id, + name: item.name, + username: item.username, + gh_username: item.gh_username, + image: item.image, + email: item.email, + emailVerified: item.emailVerified, + createdAt: item.createdAt, + updatedAt: item.updatedAt, + }; + }), + }); } catch {} return NextResponse.json({ error: "未知错误" }, { status: 500 }); } diff --git a/app/app/(auth)/login/user-login-button.tsx b/app/app/(auth)/login/user-login-button.tsx index 4862ce310..d54b0a04b 100644 --- a/app/app/(auth)/login/user-login-button.tsx +++ b/app/app/(auth)/login/user-login-button.tsx @@ -8,11 +8,16 @@ export default function UserLoginButton() { const [loading, setLoading] = useState(false); const nameInput = useRef(null); + const passwordInput = useRef(null); const emailInput = useRef(null); const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(false); - const handleComposition = (e: React.CompositionEvent) => { + const handleNameComposition = ( + e: React.CompositionEvent, + ) => { if (e.type === "compositionend") { setUsername(e.currentTarget.value); } @@ -23,6 +28,12 @@ export default function UserLoginButton() { } setUsername(e.target.value); }; + const onPasswordChange = (e: React.ChangeEvent) => { + if ((e.nativeEvent as InputEvent).isComposing) { + return; + } + setPassword(e.target.value); + }; const onSubmitHandler = async (e: React.FormEvent) => { // handle yow submition setLoading(true); @@ -40,6 +51,7 @@ export default function UserLoginButton() { } else { result = await signIn("credentials", { username: username, + password: password, redirect: false, }); } @@ -93,7 +105,7 @@ export default function UserLoginButton() { ref={nameInput} // value={username} onCompositionStart={(e) => e.preventDefault()} - onCompositionEnd={handleComposition} + onCompositionEnd={handleNameComposition} onChange={onNameChange} // required placeholder="输入姓名、拼音或邮箱" @@ -109,6 +121,29 @@ export default function UserLoginButton() { } `} /> + e.preventDefault()} + // onCompositionEnd={handleComposition} + onChange={onPasswordChange} + // required + placeholder="密码验证,测试阶段" + className={`${ + loading + ? "cursor-not-allowed bg-stone-50 dark:bg-stone-800" + : "bg-white hover:bg-stone-50 active:bg-stone-100 dark:bg-black dark:hover:border-white dark:hover:bg-black" + } group my-2 flex h-10 w-full items-center justify-center space-x-2 rounded-md border border-stone-200 transition-colors duration-75 focus:outline-none dark:border-stone-700 + ${ + error + ? "focus:invalid:border-red-500 focus:invalid:ring-red-500" + : "" + } + `} + /> tag through the object. credentials: { username: { label: "Username", type: "text", placeholder: "输入姓名或邮箱" }, - // password: { label: "Password", type: "password" } + password: { label: "Password", type: "password", placeholder: "密码验证,测试阶段" } }, // @ts-ignore async authorize(credential, req) { const username = cleanUpString(`${credential?.username}`); + const password = cleanPassword(`${credential?.password}`); // 验证用户名 - // console.log(credential, username, '==============3') + console.log(credential, 'p', password, '==============3') // 判断姓名格式是否符合要求,不符合则拒绝 if (username && isName(username)) { // Any object returned will be saved in `user` property of the JWT @@ -89,6 +90,12 @@ export const authOptions: NextAuthOptions = { } else { user['name'] = username; } + if (password) { + user['password'] = password; + // 如果有密码,则启用密码验证,查询数据库,否则失败 + return await validatePassword(user); + } + return await insertUser(user) ?? user } else { // If you return null then an error will be displayed advising the user to check their details. @@ -183,76 +190,37 @@ export async function VerifiedAdminUser() { return !!(name && ADMIN_LIST.includes(name)); } -// export function withSiteAuth(action: any) { -// return async ( -// formData: FormData | null, -// siteId: string, -// key: string | null, -// ) => { -// const session = await getSession(); -// if (!session) { -// return { -// error: "Not authenticated", -// }; -// } -// const site = await prisma.site.findUnique({ -// where: { -// id: siteId, -// }, -// }); -// if (!site || site.userId !== session.user.id) { -// return { -// error: "Not authorized", -// }; -// } -// -// return action(formData, site, key); -// }; -// } -// -// export function withPostAuth(action: any) { -// return async ( -// formData: FormData | null, -// postId: string, -// key: string | null, -// ) => { -// const session = await getSession(); -// if (!session?.user.id) { -// return { -// error: "Not authenticated", -// }; -// } -// const post = await prisma.post.findUnique({ -// where: { -// id: postId, -// }, -// include: { -// site: true, -// }, -// }); -// if (!post || post.userId !== session.user.id) { -// return { -// error: "Post not found", -// }; -// } -// -// return action(formData, post, key); -// }; -// } +export async function validatePassword(user: {[key: string]: string}): Promise { + + const existingUser = await existUser(user); + console.log('------', 'existUser', existUser) + + if (!existingUser) { + throw new Error("用户名或密码不正确"); + } + if (existingUser.password == null) { + throw new Error("未设置密码"); + } + if (!comparePassword(user.passowrd, existingUser.password)) { + throw new Error("用户名或密码不正确") + } else { + return existingUser; + } +} async function existUser(user: {[key: string]: string} | User ) { - const conditions = []; - if (user?.name) { - conditions.push({ name: user.name }); - } - if (user?.email) { - conditions.push({ email: user.email }); - } - return conditions.length ? await prisma.user.findFirst({ - where: { - AND: conditions, - }, - }) : null + const conditions = []; + if (user?.name) { + conditions.push({ name: user.name }); + } + if (user?.email) { + conditions.push({ email: user.email }); + } + return conditions.length ? await prisma.user.findFirst({ + where: { + AND: conditions, + }, + }) : null } export async function insertUser(user: {[key: string]: string}) { @@ -287,6 +255,15 @@ function cleanUpString(input: string): string { } } +function cleanPassword(input: string): string { + try { + // 去除前后空格 + return input.trim() + } + catch { + return ''; + } +} From ef5638e427903827b8005dcfe9a70408f3a33961 Mon Sep 17 00:00:00 2001 From: sijinhui Date: Thu, 18 Apr 2024 16:56:09 +0800 Subject: [PATCH 2/2] update login --- app/api/logs/[[...path]]/route.ts | 1 - .../components/users-table.modules.scss | 9 - app/app/(admin)/components/users-table.tsx | 1 - app/app/(auth)/login/user-login-button.tsx | 309 +++++++++++++----- app/app/login.scss | 16 +- lib/auth.ts | 65 ++-- 6 files changed, 267 insertions(+), 134 deletions(-) delete mode 100644 app/app/(admin)/components/users-table.modules.scss diff --git a/app/api/logs/[[...path]]/route.ts b/app/api/logs/[[...path]]/route.ts index 900b6d680..a3473a67f 100644 --- a/app/api/logs/[[...path]]/route.ts +++ b/app/api/logs/[[...path]]/route.ts @@ -1,6 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; import prisma from "@/lib/prisma"; -import { insertUser } from "@/lib/auth"; import { getTokenLength } from "@/lib/utils"; async function handle( diff --git a/app/app/(admin)/components/users-table.modules.scss b/app/app/(admin)/components/users-table.modules.scss deleted file mode 100644 index 1820de0d1..000000000 --- a/app/app/(admin)/components/users-table.modules.scss +++ /dev/null @@ -1,9 +0,0 @@ - -input { - text-align: left !important -} - - -.ant-input { - text-align: left !important -} \ No newline at end of file diff --git a/app/app/(admin)/components/users-table.tsx b/app/app/(admin)/components/users-table.tsx index 98d2e5882..203185294 100644 --- a/app/app/(admin)/components/users-table.tsx +++ b/app/app/(admin)/components/users-table.tsx @@ -1,6 +1,5 @@ "use client"; -import "./users-table.modules.scss"; import React, { Dispatch, SetStateAction, useEffect, useState } from "react"; import { User } from "@prisma/client"; import { diff --git a/app/app/(auth)/login/user-login-button.tsx b/app/app/(auth)/login/user-login-button.tsx index d54b0a04b..7b8b258bc 100644 --- a/app/app/(auth)/login/user-login-button.tsx +++ b/app/app/(auth)/login/user-login-button.tsx @@ -3,17 +3,79 @@ import { signIn } from "next-auth/react"; import React, { useState, useEffect, useRef, use } from "react"; import { isName } from "@/lib/auth_list"; +import { Form, Input, InputRef } from "antd"; +import { UserOutlined, MailOutlined } from "@ant-design/icons"; +import type { FormProps } from "antd"; +import { SignInOptions } from "next-auth/react"; export default function UserLoginButton() { const [loading, setLoading] = useState(false); - - const nameInput = useRef(null); - const passwordInput = useRef(null); + const [loginForm] = Form.useForm(); + const nameInput = useRef(null); + const passwordInput = useRef(null); const emailInput = useRef(null); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(false); + type FieldType = { + username?: string; + password?: string; + email?: string; + }; + const onFinish: FormProps["onFinish"] = (values) => { + setLoading(true); + let signInOptions: SignInOptions = { + redirect: false, + }; + let loginProvider = ""; + + if (values.email) { + loginProvider = "email"; + signInOptions = { ...signInOptions, email: values.email }; + } else { + loginProvider = "credentials"; + signInOptions = { + ...signInOptions, + username: values.username, + password: values.password ?? "", + }; + } + signIn(loginProvider, signInOptions).then((result) => { + setLoading(false); + if (!result?.error) { + window.location.href = + result?.url && result.url.includes("verify") ? result.url : "/"; + } else { + const errorParts = result.error.split(","); + if (errorParts.length > 1) { + loginForm.setFields([ + { + name: errorParts[0], + errors: [errorParts[1]], + }, + ]); + } else { + loginForm.setFields([ + { + name: "password", + errors: [result.error], + }, + ]); + } + } + console.log("response,", result); + }); + + setLoading(false); + console.log("Success:", values); + }; + + const onFinishFailed: FormProps["onFinishFailed"] = ( + errorInfo, + ) => { + console.log("Failed:", errorInfo); + }; const handleNameComposition = ( e: React.CompositionEvent, @@ -68,10 +130,11 @@ export default function UserLoginButton() { if (nameInput.current) { if (!isName(username)) { setError(true); - nameInput.current.setCustomValidity("用户名校验失败"); + // nameInput + // nameInput.current.setCustomValidity("用户名校验失败"); } else { setError(false); - nameInput.current.setCustomValidity(""); + // nameInput.current.setCustomValidity(""); } } // console.log("username:", username); @@ -79,102 +142,176 @@ export default function UserLoginButton() { return ( <> - {/* - This example requires updating your template: - - ``` - - - ``` - */} - -
-
+
-
- e.preventDefault()} - onCompositionEnd={handleNameComposition} - onChange={onNameChange} - // required - placeholder="输入姓名、拼音或邮箱" - className={`${ - loading - ? "cursor-not-allowed bg-stone-50 dark:bg-stone-800" - : "bg-white hover:bg-stone-50 active:bg-stone-100 dark:bg-black dark:hover:border-white dark:hover:bg-black" - } group my-2 flex h-10 w-full items-center justify-center space-x-2 rounded-md border border-stone-200 transition-colors duration-75 focus:outline-none dark:border-stone-700 - ${ - error - ? "focus:invalid:border-red-500 focus:invalid:ring-red-500" - : "" - } - `} - /> - { + if (value && !isName(value)) { + return Promise.reject( + new Error("Invalid username format!"), + ); + } + const email_value = loginForm.getFieldValue("email"); + if (!value && !email_value) { + return Promise.reject( + new Error("Please input your username!"), + ); + } + if (value && email_value) { + return Promise.reject(new Error("Field must be unique!")); + } + const password_value = loginForm.getFieldValue("password"); + if (!value && password_value) { + return Promise.reject( + new Error("Please input your username!"), + ); + } + }, + }, + ]} + > + e.preventDefault()} - // onCompositionEnd={handleComposition} - onChange={onPasswordChange} + // onCompositionEnd={handleNameComposition} + // onChange={onNameChange} // required - placeholder="密码验证,测试阶段" - className={`${ - loading - ? "cursor-not-allowed bg-stone-50 dark:bg-stone-800" - : "bg-white hover:bg-stone-50 active:bg-stone-100 dark:bg-black dark:hover:border-white dark:hover:bg-black" - } group my-2 flex h-10 w-full items-center justify-center space-x-2 rounded-md border border-stone-200 transition-colors duration-75 focus:outline-none dark:border-stone-700 - ${ - error - ? "focus:invalid:border-red-500 focus:invalid:ring-red-500" - : "" - } - `} + autoComplete="off" + prefix={} + placeholder="输入姓名、拼音或邮箱" + className={ + "text-sm font-medium text-stone-600 dark:text-stone-400" + } + // className={`${ + // loading + // ? "cursor-not-allowed bg-stone-50 dark:bg-stone-800" + // : "bg-white hover:bg-stone-50 active:bg-stone-100 dark:bg-black dark:hover:border-white dark:hover:bg-black" + // } group my-2 flex h-10 w-full items-center justify-center space-x-2 rounded-md border border-stone-200 transition-colors duration-75 focus:outline-none dark:border-stone-700 + // ${ + // error + // ? "focus:invalid:border-red-500 focus:invalid:ring-red-500" + // : "" + // } + // `} /> - + + + // label="Password" + name="password" + rules={[ + { + validator: async (_, value) => { + if (value) { + if (value.length < 6) { + return Promise.reject( + new Error("Password must be at least 6 characters!"), + ); + } + } + }, + }, + ]} + > + e.preventDefault()} + // // onCompositionEnd={handleComposition} + // onChange={onPasswordChange} + autoComplete="off" + // // required + placeholder="密码验证,测试阶段" + className={ + "text-sm font-medium text-stone-600 dark:text-stone-400" + } + // className={`${ + // loading + // ? "cursor-not-allowed bg-stone-50 dark:bg-stone-800" + // : "bg-white hover:bg-stone-50 active:bg-stone-100 dark:bg-black dark:hover:border-white dark:hover:bg-black" + // } group my-2 flex h-10 w-full items-center justify-center space-x-2 rounded-md border border-stone-200 transition-colors duration-75 focus:outline-none dark:border-stone-700 + // ${ + // error + // ? "focus:invalid:border-red-500 focus:invalid:ring-red-500" + // : "" + // } + // `} + /> + + { + const username_value = loginForm.getFieldValue("username"); + if (value && username_value) { + return Promise.reject(new Error("Field must be unique!")); + } + }, + }, + ]} + > + e.preventDefault()} + // onCompositionStart={(e) => e.preventDefault()} // onCompositionEnd={handleComposition} // onChange={onNameChange} // required + prefix={} placeholder="邮箱验证,测试阶段" - className={`${ - loading - ? "cursor-not-allowed bg-stone-50 dark:bg-stone-800" - : "bg-white hover:bg-stone-50 active:bg-stone-100 dark:bg-black dark:hover:border-white dark:hover:bg-black" - } group my-2 flex h-10 w-full items-center justify-center space-x-2 rounded-md border border-stone-200 transition-colors duration-75 focus:outline-none dark:border-stone-700 - ${ - error - ? "focus:invalid:border-red-500 focus:invalid:ring-red-500" - : "" - } - `} + className={ + "text-sm font-medium text-stone-600 dark:text-stone-400" + } + // className={`${ + // loading + // ? "cursor-not-allowed bg-stone-50 dark:bg-stone-800" + // : "bg-white hover:bg-stone-50 active:bg-stone-100 dark:bg-black dark:hover:border-white dark:hover:bg-black" + // } group my-2 flex h-10 w-full items-center justify-center space-x-2 rounded-md border border-stone-200 transition-colors duration-75 focus:outline-none dark:border-stone-700 + // ${ + // error + // ? "focus:invalid:border-red-500 focus:invalid:ring-red-500" + // : "" + // } + // `} /> - {/*{error &&

{error}

}*/} -
+
-
+ -
-
+ +
{/**/} diff --git a/app/app/login.scss b/app/app/login.scss index a91bef414..5fb5c9a00 100644 --- a/app/app/login.scss +++ b/app/app/login.scss @@ -14,7 +14,7 @@ border: 0; border-top: 1px solid var(--color-separator); display: block; - margin: 2rem auto 1rem; + margin: 1rem auto 1rem; overflow: visible } @@ -47,7 +47,7 @@ } .signin form input[type],.signin>div input[type] { - margin-bottom: .5rem + //margin-bottom: .5rem } .signin form button,.signin>div button { @@ -73,7 +73,7 @@ html { .login-form { background-color: #fff; width: 350px; - height: 500px; + height: 550px; border-radius: 15px; /* 定位到中心 */ @@ -86,4 +86,12 @@ html { /* 毛玻璃 */ backdrop-filter: blur(10px); /* 应用模糊效果 */ background-color: rgba(255, 255, 255, 0.5); /* 半透明的白色背景 */ -} \ No newline at end of file +} + +input { + text-align: left !important +} + +#login-form input:-webkit-autofill { + transition: background-color 5000s ease-in-out 0s; +} diff --git a/lib/auth.ts b/lib/auth.ts index ba0d4b4c6..828498e3a 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -80,7 +80,7 @@ export const authOptions: NextAuthOptions = { const username = cleanUpString(`${credential?.username}`); const password = cleanPassword(`${credential?.password}`); // 验证用户名 - console.log(credential, 'p', password, '==============3') + // console.log(credential, 'p', password, '==============3') // 判断姓名格式是否符合要求,不符合则拒绝 if (username && isName(username)) { // Any object returned will be saved in `user` property of the JWT @@ -90,17 +90,21 @@ export const authOptions: NextAuthOptions = { } else { user['name'] = username; } - if (password) { - user['password'] = password; - // 如果有密码,则启用密码验证,查询数据库,否则失败 - return await validatePassword(user); + // 目前用户不存在,则会创建新用户。 + let existingUser = await existUser(user); // await insertUser(user) + if (!existingUser) { + // 如果不存在,则报错 + // throw new Error("用户查询失败") + // 如果不存在,则创建 + existingUser = await insertUser(user); } - - return await insertUser(user) ?? user + // 有密码就校验密码,没有就直接返回用户 + password && validatePassword(password, existingUser.password); + return existingUser; } else { // If you return null then an error will be displayed advising the user to check their details. // return null - throw new Error("用户名校验失败") + throw new Error("username,用户名校验失败") // You can also Reject this callback with an Error thus the user will be sent to the error page with the error message as a query parameter } } @@ -190,21 +194,15 @@ export async function VerifiedAdminUser() { return !!(name && ADMIN_LIST.includes(name)); } -export async function validatePassword(user: {[key: string]: string}): Promise { - - const existingUser = await existUser(user); - console.log('------', 'existUser', existUser) - - if (!existingUser) { - throw new Error("用户名或密码不正确"); +export function validatePassword(password: string, hashPassword: string | null | undefined ): boolean | void { + if (!hashPassword) { + throw new Error("password,未设置密码"); } - if (existingUser.password == null) { - throw new Error("未设置密码"); - } - if (!comparePassword(user.passowrd, existingUser.password)) { - throw new Error("用户名或密码不正确") + + if (!comparePassword(password, hashPassword)) { + throw new Error("password,用户名或密码不正确") } else { - return existingUser; + return true; } } @@ -225,19 +223,20 @@ async function existUser(user: {[key: string]: string} | User ) { export async function insertUser(user: {[key: string]: string}) { try { - const existingUser = await existUser(user); - // console.log('[LOG]', existingUser, user, '=======') - if (!existingUser) { - return await prisma.user.create({ - data: user - }) - } else { - // console.log('user==========', existingUser) - return existingUser; - } + return await prisma.user.create({ + data: user + }) + // const existingUser = await existUser(user); + // // console.log('[LOG]', existingUser, user, '=======') + // if (!existingUser) { + // + // } else { + // // console.log('user==========', existingUser) + // return existingUser; + // } } catch (e) { - console.log('[Prisma Error]', e); - return false; + throw new Error("username,用户创建失败"); + // return false; } }