Skip to content

Commit

Permalink
feat: Password validation & login and logout
Browse files Browse the repository at this point in the history
  • Loading branch information
NriotHrreion committed Jul 11, 2024
1 parent 3307a3f commit 46d39b0
Show file tree
Hide file tree
Showing 19 changed files with 7,832 additions and 7,424 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Default password: 123456
# Remember to change this password after setting up the app
PASSWORD=14e1b600b1fd579f47433b88e8d85291
94 changes: 93 additions & 1 deletion app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,95 @@
"use client";

import { useEffect } from "react";
import axios from "axios";
import md5 from "md5";
import Cookie from "js-cookie";
import { Button } from "@nextui-org/button";
import { Card } from "@nextui-org/card";
import { Image } from "@nextui-org/image";
import * as z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { toast } from "react-toastify";
import { useRouter } from "next/navigation";

import ThemeSwitch from "@/components/theme-switch";
import PasswordInput from "@/components/password-input";
import { BaseResponseData } from "@/types";
import { tokenStorageKey } from "@/lib/global";

const schema = z.object({
password: z.string().min(1, { message: "请输入访问密码" }),
});

interface AuthResponseData extends BaseResponseData {
token?: string
}

export default function Page() {
return <></>;
const router = useRouter();
const form = useForm({
resolver: zodResolver(schema),
defaultValues: {
password: ""
}
});

const handleLogin = async ({ password }: z.infer<typeof schema>) => {
try {
const { data } = await axios.post<AuthResponseData>("/api/auth", { password: md5(password) });
const { status, token } = data;

if(status === 401) throw new Error("访问密码错误");
if(status === 403) {
router.push("/");
throw new Error("已登录,无法重复登录");
}
if(status === 500) throw new Error("服务器错误");
if(!token) throw new Error("服务器未返回token");

Cookie.set(tokenStorageKey, token);
toast.success("登录成功");
router.push("/x/root");
} catch (err) {
toast.error("登录失败: "+ err);
}
};

useEffect(() => {
if(Cookie.get(tokenStorageKey)) router.push("/");
}, []);

return (
<div className="h-[100vh] flex flex-col justify-center items-center">
<Card className="w-[500px] h-fit p-6">
<div className="my-16 flex flex-col items-center cursor-default">
<Image
alt="logo"
src="/icon.png"
className="w-[70px] mb-4"
style={{ imageRendering: "pixelated" }}/>
<h1 className="font-bold text-2xl mb-2">登录 Ferrum</h1>
<p className=" text-default-400 text-sm">Explore throughout your server.</p>
</div>

<form className="flex flex-col gap-4" onSubmit={form.handleSubmit(handleLogin)}>
<PasswordInput
{...form.register("password")}
isRequired
label="访问密码"
labelPlacement="outside"/>
<Button color="primary" type="submit">
登录
</Button>
</form>
</Card>

<div className="w-[500px] p-3 flex justify-between">
<span className="text-default-400 text-sm">Copyright &copy; {new Date().getFullYear()} NoahHrreion</span>

<ThemeSwitch size="sm"/>
</div>
</div>
);
}
14 changes: 10 additions & 4 deletions app/(pages)/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { Metadata } from "next";
"use client";

export const metadata: Metadata = {
title: "仪表盘",
};
import { useEffect } from "react";

import { useDetectCookie } from "@/hooks/useDetectCookie";

export default function Page() {
useEffect(() => {
document.title = "Ferrum - 仪表盘";
}, []);

useDetectCookie();

return <></>;
}
14 changes: 10 additions & 4 deletions app/(pages)/settings/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { Metadata } from "next";
"use client";

export const metadata: Metadata = {
title: "设置",
};
import { useEffect } from "react";

import { useDetectCookie } from "@/hooks/useDetectCookie";

export default function Page() {
useEffect(() => {
document.title = "Ferrum - 设置";
}, []);

useDetectCookie();

return <></>;
}
3 changes: 3 additions & 0 deletions app/(pages)/x/[[...path]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Input } from "@nextui-org/input";

import Navbar from "@/components/explorer/navbar";
import { useExplorer } from "@/hooks/useExplorer";
import { useDetectCookie } from "@/hooks/useDetectCookie";

interface FileExplorerProps {
params: {
Expand Down Expand Up @@ -34,6 +35,8 @@ export default function Page({ params }: FileExplorerProps) {
document.title = "Ferrum - "+ explorer.stringifyPath();
}, []);

useDetectCookie();

return (
<div className="w-full h-full flex flex-col items-center space-y-3">
<Input
Expand Down
30 changes: 30 additions & 0 deletions app/api/auth/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* eslint-disable no-console */
import { NextRequest, NextResponse } from "next/server";
import md5 from "md5";

import { generateToken } from "@/lib/token";
import { tokenStorageKey } from "@/lib/global";

interface AuthRequestData {
password: string
}

export async function POST(req: NextRequest) {
try {
if(req.cookies.get(tokenStorageKey)) return NextResponse.json({ status: 403 });

const { password } = await req.json() as AuthRequestData;

if(!password) return NextResponse.json({ status: 400 });
const md5Password = md5(password);

if(md5Password !== process.env["PASSWORD"]) return NextResponse.json({ status: 401 });
const token = generateToken(md5Password);

return NextResponse.json({ status: 200, token });
} catch (err) {
console.log("[Server: /api/auth/login] "+ err);

return NextResponse.json({ status: 500 });
}
}
2 changes: 2 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import "@/styles/globals.css";
import "@/styles/toastify.css";
import "react-toastify/dist/ReactToastify.css";
import { Metadata, Viewport } from "next";
import { clsx } from "clsx";

Expand Down
14 changes: 13 additions & 1 deletion app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
import { cookies } from "next/headers";
import { redirect } from "next/navigation";

import { tokenStorageKey } from "@/lib/global";
import { validateToken } from "@/lib/token";

export default function Page() {
return <>{/** @todo */}</>;
const cookieStore = cookies();
const token = cookieStore.get(tokenStorageKey);

if(!token || !validateToken(token.value)) redirect("/login");

// default redirection
redirect("/x/root");
}
9 changes: 8 additions & 1 deletion app/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { NextUIProvider } from "@nextui-org/system";
import { useRouter } from "next/navigation";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import { ThemeProviderProps } from "next-themes/dist/types";
import { ToastContainer } from "react-toastify";

export interface ProvidersProps {
children: React.ReactNode;
Expand All @@ -16,7 +17,13 @@ export function Providers({ children, themeProps }: ProvidersProps) {

return (
<NextUIProvider navigate={router.push}>
<NextThemesProvider {...themeProps}>{children}</NextThemesProvider>
<NextThemesProvider {...themeProps}>
{children}
<ToastContainer
toastClassName="!bg-content1 !text-default-foreground !shadow-lg border border-default-100"
position="bottom-right"
hideProgressBar/>
</NextThemesProvider>
</NextUIProvider>
);
}
36 changes: 13 additions & 23 deletions components/nav.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import React, { useState, useEffect } from "react";
import React, { useState } from "react";
import Cookies from "js-cookie";
import {
Navbar,
NavbarBrand,
Expand All @@ -9,19 +10,20 @@ import {
} from "@nextui-org/navbar";
import { Image } from "@nextui-org/image";
import { Button } from "@nextui-org/button";
import { Switch } from "@nextui-org/switch";
import Link from "next/link";
import { Sun, Moon, LogOut, Gauge, Folders, Settings2 } from "lucide-react";
import { useTheme } from "next-themes";
import { LogOut, Gauge, Folders, Settings2 } from "lucide-react";
import { usePathname, useRouter } from "next/navigation";
import { toast } from "react-toastify";

import ThemeSwitch from "./theme-switch";

import { useExplorer } from "@/hooks/useExplorer";
import { tokenStorageKey } from "@/lib/global";

type NavPage = "dashboard" | "explorer" | "settings";

const Nav: React.FC = () => {
const router = useRouter();
const { setTheme, theme } = useTheme();
const pathname = usePathname();
const explorer = useExplorer();
const [page, setPage] = useState<NavPage | null>(() => {
Expand All @@ -38,20 +40,13 @@ const Nav: React.FC = () => {
return null;
}
});
const [mouted, setMouted] = useState<boolean>(false);

const handleSwitchTheme = (value: boolean) => {
value
? setTheme("light")
: setTheme("dark");
const handleLogout = () => {
Cookies.remove(tokenStorageKey);
toast.success("登出成功");
router.refresh();
};

useEffect(() => {
setMouted(true);
}, []);

if(!page) router.push("/");

return (
<Navbar
classNames={{
Expand Down Expand Up @@ -98,15 +93,10 @@ const Nav: React.FC = () => {
</NavbarContent>
<NavbarContent justify="end">
<NavbarItem>
{mouted && <Switch
size="md"
startContent={<Sun size={13}/>}
endContent={<Moon size={13}/>}
defaultSelected={theme === "light"}
onValueChange={(value) => handleSwitchTheme(value)}/>}
<ThemeSwitch />
</NavbarItem>
<NavbarItem>
<Button size="sm">
<Button size="sm" onClick={() => handleLogout()}>
登出
<LogOut size={15}/>
</Button>
Expand Down
29 changes: 29 additions & 0 deletions components/password-input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* eslint-disable react/display-name */
"use client";

import React, { forwardRef, useState } from "react";
import { Eye, EyeOff } from "lucide-react";
import { Input, InputProps } from "@nextui-org/input";

const PasswordInput = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
const [passwordVisible, setPasswordVisible] = useState<boolean>(false);

return (
<Input
endContent={
<button className="outline-none" type="button" onClick={() => setPasswordVisible((current) => !current)}>
{
passwordVisible
? <EyeOff className="pointer-events-none"/>
: <Eye className="pointer-events-none"/>
}
</button>
}
type={passwordVisible ? "text" : "password"}
autoComplete="password"
ref={ref}
{...props}/>
);
})

export default PasswordInput;
34 changes: 34 additions & 0 deletions components/theme-switch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"use client";

import React, { useState, useEffect } from "react";
import { useTheme } from "next-themes";
import { Switch } from "@nextui-org/switch";
import { Sun, Moon } from "lucide-react";

interface ThemeSwitchProps {
size?: "sm" | "md" | "lg"
}

const ThemeSwitch: React.FC<ThemeSwitchProps> = ({ size }) => {
const { setTheme, theme } = useTheme();
const [mouted, setMouted] = useState<boolean>(false);

const handleSwitchTheme = (value: boolean) => {
value
? setTheme("light")
: setTheme("dark");
};

useEffect(() => {
setMouted(true);
}, []);

return mouted && <Switch
size={size ?? "md"}
startContent={<Sun size={13}/>}
endContent={<Moon size={13}/>}
defaultSelected={theme === "light"}
onValueChange={(value) => handleSwitchTheme(value)}/>;
}

export default ThemeSwitch;
13 changes: 13 additions & 0 deletions hooks/useDetectCookie.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useEffect } from "react";
import { useRouter } from "next/navigation";
import Cookies from "js-cookie";

import { tokenStorageKey } from "@/lib/global";

export function useDetectCookie() {
const router = useRouter();

useEffect(() => {
if(!Cookies.get(tokenStorageKey)) router.push("/");
});
}
2 changes: 2 additions & 0 deletions lib/global.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const version = "0.0.1";
export const tokenStorageKey = "ferrum-token";
Loading

0 comments on commit 46d39b0

Please sign in to comment.