mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-10-01 23:56:39 +08:00
update login
This commit is contained in:
parent
714ad21b74
commit
ef5638e427
@ -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(
|
||||
|
@ -1,9 +0,0 @@
|
||||
|
||||
input {
|
||||
text-align: left !important
|
||||
}
|
||||
|
||||
|
||||
.ant-input {
|
||||
text-align: left !important
|
||||
}
|
@ -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 {
|
||||
|
@ -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<HTMLInputElement>(null);
|
||||
const passwordInput = useRef<HTMLInputElement>(null);
|
||||
const [loginForm] = Form.useForm();
|
||||
const nameInput = useRef<InputRef>(null);
|
||||
const passwordInput = useRef<InputRef>(null);
|
||||
const emailInput = useRef<HTMLInputElement>(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<FieldType>["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<FieldType>["onFinishFailed"] = (
|
||||
errorInfo,
|
||||
) => {
|
||||
console.log("Failed:", errorInfo);
|
||||
};
|
||||
|
||||
const handleNameComposition = (
|
||||
e: React.CompositionEvent<HTMLInputElement>,
|
||||
@ -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:
|
||||
|
||||
```
|
||||
<html class="h-full bg-white">
|
||||
<body class="h-full">
|
||||
```
|
||||
*/}
|
||||
|
||||
<div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
|
||||
<form
|
||||
<div className="mt-6 sm:mx-auto sm:w-full sm:max-w-sm">
|
||||
<Form
|
||||
className="space-y-6"
|
||||
action="#"
|
||||
method="POST"
|
||||
// action="#"
|
||||
// method="POST"
|
||||
autoComplete="off"
|
||||
onSubmit={onSubmitHandler}
|
||||
onFinish={onFinish}
|
||||
onFinishFailed={onFinishFailed}
|
||||
size="large"
|
||||
form={loginForm}
|
||||
id="login-form"
|
||||
// onSubmit={onSubmitHandler}
|
||||
>
|
||||
<div>
|
||||
<div className="mt-2">
|
||||
<input
|
||||
id="username"
|
||||
name="username"
|
||||
type="username"
|
||||
ref={nameInput}
|
||||
// value={username}
|
||||
onCompositionStart={(e) => 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"
|
||||
: ""
|
||||
}
|
||||
`}
|
||||
/>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
ref={passwordInput}
|
||||
value={password}
|
||||
<Form.Item
|
||||
name="username"
|
||||
rules={[
|
||||
{
|
||||
validator: async (_, value) => {
|
||||
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!"),
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
// id="basic_username"
|
||||
// name="username"
|
||||
// type="username"
|
||||
// ref={nameInput}
|
||||
// // value={username}
|
||||
// onCompositionStart={(e) => 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={<UserOutlined style={{ color: "rgba(0,0,0,.25)" }} />}
|
||||
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"
|
||||
// : ""
|
||||
// }
|
||||
// `}
|
||||
/>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
ref={emailInput}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<FieldType>
|
||||
// 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!"),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input.Password
|
||||
// id="basic_password"
|
||||
// name="password"
|
||||
// type="password"
|
||||
// status={error ? "error" : ""}
|
||||
// ref={passwordInput}
|
||||
// value={password}
|
||||
// // onCompositionStart={(e) => 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"
|
||||
// : ""
|
||||
// }
|
||||
// `}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="email"
|
||||
rules={[
|
||||
{
|
||||
type: "email",
|
||||
message: "The input is not valid E-mail!",
|
||||
},
|
||||
{
|
||||
validator: async (_, value) => {
|
||||
const username_value = loginForm.getFieldValue("username");
|
||||
if (value && username_value) {
|
||||
return Promise.reject(new Error("Field must be unique!"));
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
// id="email"
|
||||
// name="email"
|
||||
// type="email"
|
||||
// ref={emailInput}
|
||||
// value={username}
|
||||
onCompositionStart={(e) => e.preventDefault()}
|
||||
// onCompositionStart={(e) => e.preventDefault()}
|
||||
// onCompositionEnd={handleComposition}
|
||||
// onChange={onNameChange}
|
||||
// required
|
||||
prefix={<MailOutlined style={{ color: "rgba(0,0,0,.25)" }} />}
|
||||
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 && <p className="mt-2 text-pink-600 text-sm">{error}</p>}*/}
|
||||
</div>
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Form.Item>
|
||||
<button
|
||||
disabled={loading}
|
||||
// onClick={(e) => handleSubmit(e)}
|
||||
// onClick={() => loginForm.submit()}
|
||||
type="submit"
|
||||
className={`${
|
||||
loading
|
||||
@ -184,8 +321,8 @@ export default function UserLoginButton() {
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
{/*</div>*/}
|
||||
</>
|
||||
|
@ -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;
|
||||
|
||||
/* 定位到中心 */
|
||||
@ -87,3 +87,11 @@ html {
|
||||
backdrop-filter: blur(10px); /* 应用模糊效果 */
|
||||
background-color: rgba(255, 255, 255, 0.5); /* 半透明的白色背景 */
|
||||
}
|
||||
|
||||
input {
|
||||
text-align: left !important
|
||||
}
|
||||
|
||||
#login-form input:-webkit-autofill {
|
||||
transition: background-color 5000s ease-in-out 0s;
|
||||
}
|
||||
|
65
lib/auth.ts
65
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<User | void> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user