diff --git a/.github/workflows/dockerToHub-dev.yml b/.github/workflows/dockerToHub-dev.yml
index 69503ae19..56cb8f194 100644
--- a/.github/workflows/dockerToHub-dev.yml
+++ b/.github/workflows/dockerToHub-dev.yml
@@ -20,7 +20,7 @@ jobs:
- name: build and deploy to Docker Hub
run: |
echo ${{ secrets.ALY_DOCKER_PASSWORD }} | docker login registry.cn-hangzhou.aliyuncs.com -u ${{ secrets.ALY_DOCKER_USERNAME }} --password-stdin
- echo "${{ secrets.DOCKER_ENV }}" > .env
+ #echo "${{ secrets.DOCKER_ENV }}" > .env
echo "COMPOSE_PROJECT_NAME=test-chatgpt-web" >> .env
#bash ./start.sh
# 替换测试镜像
@@ -62,8 +62,8 @@ jobs:
script: |
mkdir -p /data/test/ChatGPT-Next-Web
cd /data/test/ChatGPT-Next-Web
- echo "${{ secrets.DOCKER_ENV }}" > .env
- echo "PORT=23001" > .env
+ echo "${{ secrets.DOCKER_ENV }}" >> .env
+ echo "PORT=23001" >> .env
# 测试分支,
echo "COMPOSE_PROJECT_NAME=test-chatgpt-web" >> .env
sed -i 's@image: registry.cn-hangzhou.aliyuncs.com/si-private/chatgpt-next-web@image: registry.cn-hangzhou.aliyuncs.com/si-private/chatgpt-next-web:test@g' docker-compose.yml
diff --git a/app/api/(user)/user/[path]/route.ts b/app/api/(user)/user/[path]/route.ts
new file mode 100644
index 000000000..95d3f0a44
--- /dev/null
+++ b/app/api/(user)/user/[path]/route.ts
@@ -0,0 +1,70 @@
+import { NextRequest, NextResponse } from "next/server";
+import prisma from "@/lib/prisma";
+import { hashPassword, comparePassword } from "@/lib/utils";
+import { getSession } from "@/lib/auth";
+
+async function handle(
+ req: NextRequest,
+ { params }: { params: { path: string } },
+) {
+ // 判断网址和请求方法
+ const method = req.method;
+ // const url = req.url;
+ const { pathname, searchParams } = new URL(req.url);
+ const searchText = searchParams.get("search");
+
+ // 校验仅当前用户支持访问
+ const session = await getSession();
+ if (params.path !== session?.user?.id) {
+ // return NextResponse.json({ error: "无权限" }, { status: 402 });
+ }
+
+ const new_password_d = await req.json();
+ // 旧密码校验
+ // @ts-expect-error
+ if (session?.user?.hasPassword) {
+ const user = await prisma.user.findUnique({
+ where: {
+ id: params.path,
+ },
+ });
+ if (
+ !(
+ new_password_d["user[old_password]"] &&
+ comparePassword(
+ new_password_d["user[old_password]"],
+ user?.password ?? "",
+ )
+ )
+ ) {
+ return NextResponse.json({ error: "密码校验失败" }, { status: 401 });
+ }
+ }
+
+ // 校验新密码规则
+ if (
+ new_password_d["user[password]"].length < 6 ||
+ new_password_d["user[password]"] !==
+ new_password_d["user[password_confirmation]"]
+ ) {
+ return NextResponse.json({ error: "密码校验失败" }, { status: 401 });
+ }
+
+ await prisma.user.update({
+ where: {
+ id: params.path,
+ },
+ data: {
+ password: hashPassword(new_password_d["user[password]"]),
+ },
+ });
+ return NextResponse.json({ result: "ok" });
+
+ // return NextResponse.json({ error: "未知错误" }, { status: 500 });
+ // return NextResponse.json({ error: "当前方法不支持" }, { status: 405 });
+}
+
+// export const GET = handle;
+// export const POST = handle;
+export const PUT = handle;
+// export const DELETE = handle;
diff --git a/app/app/(auth)/layout.tsx b/app/app/(auth)/layout.tsx
index 60f219ccb..905e71975 100644
--- a/app/app/(auth)/layout.tsx
+++ b/app/app/(auth)/layout.tsx
@@ -1,6 +1,7 @@
import "@/app/app/login.scss";
import { Metadata } from "next";
import { ReactNode } from "react";
+import Image from "next/image";
// import { VerifiedUser } from "@/lib/auth";
// import { redirect } from "next/navigation";
@@ -22,7 +23,19 @@ export default async function AuthLayout({
return (
- {children}
+
+
+
+ Sign in to your account
+
+ {children}
+
);
diff --git a/app/app/(auth)/login/login-button.tsx b/app/app/(auth)/login/login-button.tsx
deleted file mode 100644
index b841c818c..000000000
--- a/app/app/(auth)/login/login-button.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-"use client";
-
-import LoadingDots from "@/app/components/icons/loading-dots";
-import { signIn } from "next-auth/react";
-import { useSearchParams } from "next/navigation";
-import { useState } from "react";
-
-export default function LoginButton() {
- const [loading, setLoading] = useState(false);
-
- // Get error message added by next/auth in URL.
- const searchParams = useSearchParams();
-
- return (
-
- );
-}
diff --git a/app/app/(auth)/login/loginByGithub.tsx b/app/app/(auth)/login/loginByGithub.tsx
new file mode 100644
index 000000000..d9a30a853
--- /dev/null
+++ b/app/app/(auth)/login/loginByGithub.tsx
@@ -0,0 +1,44 @@
+"use client";
+
+import LoadingDots from "@/app/components/icons/loading-dots";
+import { signIn } from "next-auth/react";
+import { useState } from "react";
+
+export default function LoginByGithub() {
+ const [loading, setLoading] = useState(false);
+
+ return (
+
+
+
+
+ );
+}
diff --git a/app/app/(auth)/login/page.tsx b/app/app/(auth)/login/page.tsx
index c4f25a877..4bddc5131 100644
--- a/app/app/(auth)/login/page.tsx
+++ b/app/app/(auth)/login/page.tsx
@@ -1,41 +1,36 @@
import Image from "next/image";
-import LoginButton from "./login-button";
-import UserLoginButton from "./user-login-button";
+import LoginByGithub from "./loginByGithub";
+import UserLoginCore from "./user-login-core";
import { Suspense } from "react";
export default function LoginPage() {
return (
-
-
-
- Sign in to your account
-
-
+ <>
+ {/*
*/}
-
- }
- >
-
-
+ {/**/}
+ {/* }*/}
+ {/*>*/}
+ {/* */}
+ {/**/}
+
-
-
-
- }
- >
-
-
+
+ {/**/}
+ {/* }*/}
+ {/*>*/}
+ {/* */}
+ {/**/}
+
+ {" "}
+ 其它登录方式{" "}
+
+
-
+ >
);
}
diff --git a/app/app/(auth)/login/set-password/page.tsx b/app/app/(auth)/login/set-password/page.tsx
new file mode 100644
index 000000000..5a2bb22e3
--- /dev/null
+++ b/app/app/(auth)/login/set-password/page.tsx
@@ -0,0 +1,157 @@
+"use client";
+import { redirect } from "next/navigation";
+// import { getSession } from "@/lib/auth";
+import { useSession } from "next-auth/react";
+import { Button, Checkbox, Form, FormProps, Input } from "antd";
+import { LockOutlined } from "@ant-design/icons";
+import React, { useState } from "react";
+import { signOut } from "next-auth/react";
+
+type LoginType = "phone" | "account";
+
+export default function SetPasswordPage() {
+ const [loading, setLoading] = useState(false);
+ const { data: session, status } = useSession();
+ const [showOldPassword, setShowOldPassword] = useState
(true);
+ const [setPasswordForm] = Form.useForm();
+ // if (typeof window !== "undefined" && loading) return null;
+ // console.log("2222222", session);
+ // @ ts-expect-error
+ // if (!session?.user?.hasPassword) {
+ // setShowOldPassword(false);
+ // }
+ // if (status === "authenticated") {
+ // console.log('55555,', session, status)
+ // // @ts-expect-error
+ // if (session?.user?.hasPassword) {
+ // setShowOldPassword(false);
+ // }
+ // }
+ // console.log('---', session)
+ type FieldType = {
+ "user[old_password]"?: string;
+ "user[password]"?: string;
+ "user[password_confirmation]"?: string;
+ };
+ const onFinish: FormProps["onFinish"] = (values) => {
+ setLoading(true);
+ // console.log('-------------', values)
+ // @ts-expect-error
+ fetch(`/api/user/${session?.user?.id}`, {
+ method: "PUT",
+ credentials: "include",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(values),
+ })
+ .then((response) => response.json())
+ .then((result) => {
+ if (result["result"] == "ok") {
+ signOut({ redirect: true, callbackUrl: "/login" });
+ }
+ console.log("--------", result);
+ });
+ };
+
+ return (
+ <>
+
+ >
+ );
+}
diff --git a/app/app/(auth)/login/user-login-button.tsx b/app/app/(auth)/login/user-login-button.tsx
deleted file mode 100644
index 20f047575..000000000
--- a/app/app/(auth)/login/user-login-button.tsx
+++ /dev/null
@@ -1,370 +0,0 @@
-"use client";
-
-import { signIn } from "next-auth/react";
-import React, { useState, useEffect, useRef, use } from "react";
-import { isName } from "@/lib/auth_list";
-import {
- Form,
- Input,
- InputRef,
- notification as notificationModule,
- NotificationArgsProps,
-} 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 [loginForm] = Form.useForm();
- const nameInput = useRef(null);
- // const passwordInput = useRef(null);
- // const emailInput = useRef(null);
- const [username, setUsername] = useState("");
- // const [password, setPassword] = useState("");
- const [notification, notificationContextHolder] =
- notificationModule.useNotification();
-
- const openNotification = (level: string, arms: NotificationArgsProps) => {
- if (level === "error") {
- notification.error({
- ...arms,
- placement: "topRight",
- });
- } else {
- notification.info({
- ...arms,
- placement: "topRight",
- });
- }
- };
-
- // 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 {
- switch (result.error) {
- case "AccessDenied":
- openNotification("error", {
- message: "登录失败",
- description: (
-
- 无权限,请确认用户名正确并等待审批
-
- 或联系管理员
-
- ),
- });
- break;
- default:
- break;
- }
-
- if (loginProvider === "credentials") {
- loginForm.setFields([
- {
- name: "username",
- errors: [result.error],
- },
- {
- name: "password",
- errors: [result.error],
- },
- ]);
- }
- if (loginProvider === "email") {
- loginForm.setFields([
- {
- name: "email",
- 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,
- // ) => {
- // if (e.type === "compositionend") {
- // setUsername(e.currentTarget.value);
- // }
- // };
- // const onNameChange = (e: React.ChangeEvent) => {
- // if ((e.nativeEvent as InputEvent).isComposing) {
- // return;
- // }
- // 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);
- // e.preventDefault();
- //
- // let result: { error: any; url: string | null } | undefined = {
- // error: null,
- // url: null,
- // };
- // if (emailInput.current && emailInput.current.value) {
- // result = await signIn("email", {
- // email: emailInput.current.value,
- // redirect: false,
- // });
- // } else {
- // result = await signIn("credentials", {
- // username: username,
- // password: password,
- // redirect: false,
- // });
- // }
- // console.log("0000000000000", result);
- // setLoading(false);
- // if (!result?.error) {
- // window.location.href =
- // result?.url && result.url.includes("verify") ? result.url : "/";
- // } else setError(true);
- // };
-
- // useEffect(() => {
- // if (!username) return;
- // if (nameInput.current) {
- // if (!isName(username)) {
- // setError(true);
- // } else {
- // setError(false);
- // }
- // }
- // }, [username]);
-
- return (
- <>
- {notificationContextHolder}
-
-
-
-
-
-
- {/* */}
- >
- );
-}
diff --git a/app/app/(auth)/login/user-login-core.tsx b/app/app/(auth)/login/user-login-core.tsx
new file mode 100644
index 000000000..5cb80f36b
--- /dev/null
+++ b/app/app/(auth)/login/user-login-core.tsx
@@ -0,0 +1,303 @@
+"use client";
+
+import { signIn } from "next-auth/react";
+import React, { useState, useEffect, useRef, use } from "react";
+import { isName } from "@/lib/auth_list";
+import {
+ Form,
+ Tabs,
+ Input,
+ InputRef,
+ notification as notificationModule,
+ NotificationArgsProps,
+} from "antd";
+import { UserOutlined, MailOutlined } from "@ant-design/icons";
+import type { FormProps, TabsProps } from "antd";
+import { SignInOptions } from "next-auth/react";
+import { getSession } from "next-auth/react";
+
+export default function UserLoginCore() {
+ const [loading, setLoading] = useState(false);
+ const [loginForm] = Form.useForm();
+ const [loginMethod, setLoginMethod] = useState<"common" | "cap">("common");
+ const [notification, notificationContextHolder] =
+ notificationModule.useNotification();
+
+ const openNotification = (level: string, arms: NotificationArgsProps) => {
+ if (level === "error") {
+ notification.error({
+ ...arms,
+ placement: "topRight",
+ });
+ } else {
+ notification.info({
+ ...arms,
+ placement: "topRight",
+ });
+ }
+ };
+
+ // 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 (loginMethod === "cap") {
+ 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) {
+ // 如果没有密码,且登录成功了,说明需要设置密码
+ let result_url =
+ result?.url && result.url.includes("verify") ? result.url : "/";
+
+ // 手动获取一遍session
+ getSession()
+ .then((value) => {
+ // @ts-expect-error
+ if (!value?.user?.hasPassword) {
+ if (result_url === "/") {
+ result_url = "/login/set-password";
+ }
+ }
+ })
+ .finally(() => {
+ window.location.href = result_url;
+ });
+ } else {
+ switch (result.error) {
+ case "AccessDenied":
+ openNotification("error", {
+ message: "登录失败",
+ description: (
+
+ 无权限,请确认用户名正确并等待审批
+
+ 或联系管理员
+
+ ),
+ });
+ break;
+ default:
+ break;
+ }
+
+ if (loginProvider === "credentials") {
+ loginForm.setFields([
+ {
+ name: "username",
+ errors: [result.error],
+ },
+ {
+ name: "password",
+ errors: [result.error],
+ },
+ ]);
+ }
+ if (loginProvider === "email") {
+ loginForm.setFields([
+ {
+ name: "email",
+ errors: [result.error],
+ },
+ ]);
+ }
+ }
+ console.log("response,", result);
+ });
+
+ setLoading(false);
+ console.log("Success:", values);
+ };
+
+ const onFinishFailed: FormProps["onFinishFailed"] = (
+ errorInfo,
+ ) => {
+ console.log("Failed:", errorInfo);
+ };
+
+ const onTabsChange = (key: "common" | "cap") => {
+ console.log(key);
+ setLoginMethod(key);
+ };
+
+ const tabItems = [
+ {
+ key: "common",
+ label: "账号密码登录",
+ children: "",
+ },
+ {
+ key: "cap",
+ label: "验证码登录",
+ children: "",
+ },
+ ];
+
+ return (
+ <>
+ {notificationContextHolder}
+
+
onTabsChange(key as "common" | "cap")}
+ >
+
+
+
+
+
+ {/**/}
+ >
+ );
+}
diff --git a/app/app/login.scss b/app/app/login.scss
index 6f749421f..d2519df83 100644
--- a/app/app/login.scss
+++ b/app/app/login.scss
@@ -73,7 +73,7 @@ html {
.login-form {
background-color: #fff;
width: 350px;
- height: 550px;
+ height: 500px;
border-radius: 15px;
/* 定位到中心 */
@@ -99,3 +99,30 @@ input {
#user-admin-table-pop_confirm input:-webkit-autofill {
transition: background-color 5000s ease-in-out 0s;
}
+
+#set-password-form input:-webkit-autofill {
+ transition: background-color 5000s ease-in-out 0s;
+}
+
+
+/* 提交按钮特定宽度 */
+
+.short-width-button {
+ width: auto !important;
+}
+
+//#set-password-form .ant-form-item {
+// margin-top: auto !important;
+// margin-bottom: auto !important;
+//}
+
+#set-password-form .ant-form-item .ant-col label {
+ font-weight: 600;
+}
+#set-password-form .ant-btn {
+ font-weight: 600;
+}
+
+#set-password-form {
+ text-align: right !important;
+}
diff --git a/lib/auth.ts b/lib/auth.ts
index f7f12944f..fbcba33c4 100644
--- a/lib/auth.ts
+++ b/lib/auth.ts
@@ -8,7 +8,6 @@ import { User } from "@prisma/client";
import { isEmail, isName } from "@/lib/auth_list";
import {createTransport} from "nodemailer";
import { comparePassword, hashPassword } from "@/lib/utils";
-import {getCurStartEnd} from "@/app/utils/custom";
const SECURE_COOKIES:boolean = !!process.env.SECURE_COOKIES;
@@ -158,6 +157,8 @@ export const authOptions: NextAuthOptions = {
id: token?.sub,
// @ts-expect-error
username: token?.user?.username || token?.user?.gh_username,
+ // @ts-expect-error
+ hasPassword: !!token?.user?.password,
};
// console.log('555555555,', session, token)
return session;
diff --git a/lib/auth_client.ts b/lib/auth_client.ts
index c9fd136a8..143480963 100644
--- a/lib/auth_client.ts
+++ b/lib/auth_client.ts
@@ -1,16 +1,21 @@
-import {isName} from "@/lib/auth_list";
+import { isName } from "@/lib/auth_list";
import { CUS_JWT } from "@/lib/auth_type";
-export async function VerifiedUser(session: CUS_JWT |null) {
+export async function VerifiedUser(session: CUS_JWT | null) {
const userId = session?.sub
const name = session?.email || session?.name
return !!(name && isName(name) && userId);
}
-export async function VerifiedAdminUser(session: CUS_JWT |null) {
+export async function VerifiedAdminUser(session: CUS_JWT | null) {
// console.log('-------', session, session?.user?.isAdmin)
return !!session?.user?.isAdmin;
// const name = session?.email || session?.name
// return !!(name && ADMIN_LIST.includes(name));
}
+
+export function VerifiedNeedSetPassword(path: string, session: CUS_JWT | null,) {
+ const need_set_pwd = !session?.user?.password
+ return path === "/login/set-password" && need_set_pwd;
+}
diff --git a/middleware.ts b/middleware.ts
index 8660f11d8..1a8c7ff79 100644
--- a/middleware.ts
+++ b/middleware.ts
@@ -1,7 +1,7 @@
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { getToken } from "next-auth/jwt";
-import { VerifiedUser, VerifiedAdminUser } from "@/lib/auth_client";
+import { VerifiedUser, VerifiedAdminUser, VerifiedNeedSetPassword } from "@/lib/auth_client";
import { CUS_JWT } from "@/lib/auth_type";
@@ -16,7 +16,6 @@ export default async function middleware(req: NextRequest) {
return NextResponse.redirect(new URL(path.replace('/app', ''), req.url), 301);
}
-
const session = await getToken({ req });
const isUser = await VerifiedUser(session as CUS_JWT);
const isAdminUser = await VerifiedAdminUser(session as CUS_JWT);
@@ -35,7 +34,7 @@ export default async function middleware(req: NextRequest) {
return NextResponse.redirect(new URL("/", req.url))
}
- if (path == '/login') {
+ if (path.startsWith('/login')) {
return NextResponse.rewrite(
new URL(`/app${path}`, req.url),
);
@@ -46,6 +45,10 @@ export default async function middleware(req: NextRequest) {
);
}
+ // if (VerifiedNeedSetPassword(path, session as CUS_JWT)) {
+ // console.log('-0-0-- 需要修改密码', )
+ // // return NextResponse.redirect(new URL("/login/set-password", req.url))
+ // }
return NextResponse.next()
}