From db8be98230fe92f80aa7275b18a9a00432ca680f Mon Sep 17 00:00:00 2001 From: sijinhui Date: Fri, 24 May 2024 17:40:35 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=99=BB=E5=BD=95=E9=80=BB?= =?UTF-8?q?=E8=BE=91,=E5=BE=85=E6=B7=BB=E5=8A=A0=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E5=AF=86=E7=A0=81=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/app/(auth)/layout.tsx | 15 +- app/app/(auth)/login/login-button.tsx | 47 --- app/app/(auth)/login/loginByGithub.tsx | 44 +++ app/app/(auth)/login/page.tsx | 57 ++-- app/app/(auth)/login/set-password/page.tsx | 35 ++ app/app/(auth)/login/user-login-button.tsx | 370 --------------------- app/app/(auth)/login/user-login-core.tsx | 292 ++++++++++++++++ app/app/login.scss | 2 +- lib/auth.ts | 3 +- lib/auth_client.ts | 11 +- middleware.ts | 9 +- 11 files changed, 428 insertions(+), 457 deletions(-) delete mode 100644 app/app/(auth)/login/login-button.tsx create mode 100644 app/app/(auth)/login/loginByGithub.tsx create mode 100644 app/app/(auth)/login/set-password/page.tsx delete mode 100644 app/app/(auth)/login/user-login-button.tsx create mode 100644 app/app/(auth)/login/user-login-core.tsx 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} +
+ Platforms Starter Kit +

+ 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 ( -
- Platforms Starter Kit -

- 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..f0fe2ff14 --- /dev/null +++ b/app/app/(auth)/login/set-password/page.tsx @@ -0,0 +1,35 @@ +"use client"; +import { redirect } from "next/navigation"; +// import { getSession } from "@/lib/auth"; +import { useSession } from "next-auth/react"; +import { Button, Checkbox, Form, Input } from "antd"; + +type LoginType = "phone" | "account"; + +export default function SetPasswordPage() { + const { data: session, status } = useSession(); + // if (typeof window !== "undefined" && loading) return null; + console.log("2222222", session); + // @ts-expect-error + if (!session?.user?.hasPassword) { + } + // else { + // redirect("/") + // } + return ( + <> +

Signed in as {}

+
需要设置一个密码
+
+ + + + ); +} 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} -
-
-
- { - 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={handleNameComposition} - // onChange={onNameChange} - // required - 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()} - // onCompositionEnd={handleComposition} - // onChange={onNameChange} - // required - 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" - // : "" - // } - // `} - /> - -
- - - - -
-
- {/*
*/} - - ); -} 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..beff8d709 --- /dev/null +++ b/app/app/(auth)/login/user-login-core.tsx @@ -0,0 +1,292 @@ +"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"; + +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 : "/"; + if (result_url === "/") { + result_url = "/login/set-password"; + } + 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")} + > +
+
+ {loginMethod === "common" && ( + <> + { + 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!"), + ); + } + }, + }, + ]} + > + + } + placeholder="输入姓名、拼音或邮箱" + className={ + "text-sm font-medium text-stone-600 dark:text-stone-400" + } + /> + + + + // 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!", + ), + ); + } + } + }, + }, + ]} + > + + + + )} + {loginMethod === "cap" && ( + <> + { + // const username_value = loginForm.getFieldValue("username"); + // if (value && username_value) { + // return Promise.reject(new Error("Field must be unique!")); + // } + // }, + // }, + ]} + > + + } + placeholder="邮箱验证,测试阶段" + className={ + "text-sm font-medium text-stone-600 dark:text-stone-400" + } + /> + + + )} +
+ + + + +
+
+ {/**/} + + ); +} diff --git a/app/app/login.scss b/app/app/login.scss index 6f749421f..2e1dbccd0 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; /* 定位到中心 */ 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..6c25db144 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() }