feat(auth): 实现基于 NextAuth.js 的管理员认证系统
添加 NextAuth.js 依赖并配置认证模块 创建登录页面和管理后台会话保护中间件 更新 README 文档说明管理员认证功能
This commit is contained in:
parent
11dc584a4e
commit
8c2b422a37
11 changed files with 493 additions and 6 deletions
|
@ -30,6 +30,15 @@ NEXT_PUBLIC_FONT_STATIC_URL=https://your-cdn-domain.com
|
|||
DOWNLOAD_URL=https://your-download-domain.com
|
||||
PREFIX=wenfeng
|
||||
|
||||
# NextAuth.js 认证配置
|
||||
AUTH_SECRET=your-super-secret-key-change-this-in-production
|
||||
NEXTAUTH_URL=http://localhost:3000
|
||||
|
||||
# 管理员账号配置
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=admin123
|
||||
ADMIN_EMAIL=admin@windfonts.com
|
||||
|
||||
# 环境标识
|
||||
NODE_ENV=development
|
||||
NEXT_PUBLIC_ENV=development
|
51
README.md
51
README.md
|
@ -11,10 +11,12 @@
|
|||
- **搜索功能**:快速查找目标字体
|
||||
|
||||
### 🛠️ 管理功能
|
||||
- **管理员认证**:基于 NextAuth.js 的安全认证系统
|
||||
- **字体上传**:支持 TTF/OTF 格式字体文件上传
|
||||
- **字体管理**:增删改查字体信息,包括分类、标签、描述等
|
||||
- **批量操作**:支持批量管理字体文件
|
||||
- **版本控制**:字体文件版本管理和更新
|
||||
- **会话管理**:安全的用户会话和登出功能
|
||||
|
||||
### 🚀 API 服务
|
||||
- **CSS API**:动态生成字体 CSS 文件,支持 Google Fonts 兼容格式
|
||||
|
@ -43,6 +45,7 @@
|
|||
- **PostgreSQL**:可靠的关系型数据库
|
||||
- **Drizzle ORM**:类型安全的 ORM
|
||||
- **Redux Toolkit**:状态管理
|
||||
- **NextAuth.js**:现代化认证解决方案
|
||||
|
||||
### 开发工具
|
||||
- **ESLint**:代码质量检查
|
||||
|
@ -79,7 +82,19 @@ DATA_BASE_NAME=windfonts
|
|||
POSTGRES_URL=postgresql://username:password@localhost:5432/windfonts
|
||||
```
|
||||
|
||||
3. 配置 OSS 存储(可选):
|
||||
3. 配置管理员认证:
|
||||
```env
|
||||
# NextAuth.js 认证配置
|
||||
AUTH_SECRET=your-super-secret-key-change-this-in-production
|
||||
NEXTAUTH_URL=http://localhost:3000
|
||||
|
||||
# 管理员账号配置
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=admin123
|
||||
ADMIN_EMAIL=admin@windfonts.com
|
||||
```
|
||||
|
||||
4. 配置 OSS 存储(可选):
|
||||
```env
|
||||
# 阿里云 OSS 配置
|
||||
OSS_ACCESS_KEY_ID=your_access_key
|
||||
|
@ -105,6 +120,15 @@ yarn dev
|
|||
|
||||
访问 [http://localhost:3000](http://localhost:3000) 查看应用。
|
||||
|
||||
### 管理员登录
|
||||
访问 [http://localhost:3000/login](http://localhost:3000/login) 进入管理员登录页面。
|
||||
|
||||
默认管理员账号:
|
||||
- 用户名:`admin`
|
||||
- 密码:`admin123`
|
||||
|
||||
登录成功后可访问 [http://localhost:3000/admin](http://localhost:3000/admin) 进入管理后台。
|
||||
|
||||
## 📜 可用脚本
|
||||
|
||||
### 开发和构建
|
||||
|
@ -122,20 +146,26 @@ yarn dev
|
|||
next-windfonts/
|
||||
├── app/ # Next.js App Router 页面
|
||||
│ ├── api/ # API 路由
|
||||
│ │ ├── auth/ # NextAuth.js 认证 API
|
||||
│ │ ├── css/ # 字体 CSS API
|
||||
│ │ └── fonts/ # 字体管理 API
|
||||
│ ├── admin/ # 管理后台页面
|
||||
│ ├── admin/ # 管理后台页面(需要认证)
|
||||
│ ├── login/ # 管理员登录页面
|
||||
│ ├── fonts/ # 字体展示页面
|
||||
│ └── docs/ # 文档页面
|
||||
├── src/
|
||||
│ ├── components/ # React 组件
|
||||
│ │ ├── admin/ # 管理后台组件
|
||||
│ │ └── providers/ # Context 提供者(包括 SessionProvider)
|
||||
│ ├── lib/ # 核心库和服务
|
||||
│ │ ├── auth.ts # NextAuth.js 配置
|
||||
│ │ ├── database/ # 数据库配置和 Schema
|
||||
│ │ ├── services/ # 业务逻辑服务
|
||||
│ │ └── config/ # 配置文件
|
||||
│ ├── hooks/ # 自定义 React Hooks
|
||||
│ ├── store/ # Redux 状态管理
|
||||
│ └── utils/ # 工具函数
|
||||
├── middleware.ts # Next.js 中间件(路由保护)
|
||||
├── drizzle/ # 数据库迁移文件
|
||||
├── posts/ # MDX 文档内容
|
||||
└── public/ # 静态资源
|
||||
|
@ -143,6 +173,23 @@ next-windfonts/
|
|||
|
||||
## 🔧 配置说明
|
||||
|
||||
### 安全配置
|
||||
|
||||
#### AUTH_SECRET 生成
|
||||
为了确保认证系统的安全性,请生成一个强随机密钥:
|
||||
|
||||
```bash
|
||||
# 使用 OpenSSL 生成随机密钥
|
||||
openssl rand -base64 32
|
||||
```
|
||||
|
||||
将生成的密钥设置为 `AUTH_SECRET` 环境变量的值。
|
||||
|
||||
#### 管理员账号安全
|
||||
- 生产环境中请务必修改默认的管理员用户名和密码
|
||||
- 建议使用强密码,包含大小写字母、数字和特殊字符
|
||||
- 定期更换管理员密码
|
||||
|
||||
### 字体分类
|
||||
- 无衬线字体
|
||||
- 衬线
|
||||
|
|
22
app/api/auth/[...nextauth]/route.ts
Normal file
22
app/api/auth/[...nextauth]/route.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright (C) 2024 WindFonts Project
|
||||
*
|
||||
* This file is part of WindFonts.
|
||||
*
|
||||
* WindFonts is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* WindFonts is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with WindFonts. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { handlers } from '@/lib/auth';
|
||||
|
||||
export const { GET, POST } = handlers;
|
|
@ -3,6 +3,7 @@ import React, { ReactNode } from 'react';
|
|||
import { MantineProvider, ColorSchemeScript } from '@mantine/core';
|
||||
import { Metadata } from 'next';
|
||||
import { theme } from '@/theme';
|
||||
import { SessionProvider } from '@/components/providers/SessionProvider';
|
||||
|
||||
import '@/styles/globals.css';
|
||||
|
||||
|
@ -56,7 +57,9 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
|||
<ColorSchemeScript />
|
||||
</head>
|
||||
<body>
|
||||
<MantineProvider theme={theme}>{children}</MantineProvider>
|
||||
<SessionProvider>
|
||||
<MantineProvider theme={theme}>{children}</MantineProvider>
|
||||
</SessionProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
|
132
app/login/page.tsx
Normal file
132
app/login/page.tsx
Normal file
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Copyright (C) 2024 WindFonts Project
|
||||
*
|
||||
* This file is part of WindFonts.
|
||||
*
|
||||
* WindFonts is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* WindFonts is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with WindFonts. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { signIn, getSession } from 'next-auth/react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import {
|
||||
Container,
|
||||
Paper,
|
||||
TextInput,
|
||||
PasswordInput,
|
||||
Button,
|
||||
Title,
|
||||
Text,
|
||||
Alert,
|
||||
Stack,
|
||||
Center,
|
||||
} from '@mantine/core';
|
||||
import { IconAlertCircle, IconLock } from '@tabler/icons-react';
|
||||
|
||||
export default function LoginPage() {
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
setError('');
|
||||
|
||||
try {
|
||||
const result = await signIn('credentials', {
|
||||
username,
|
||||
password,
|
||||
redirect: false,
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
setError('用户名或密码错误');
|
||||
} else {
|
||||
// 验证登录状态
|
||||
const session = await getSession();
|
||||
if (session) {
|
||||
router.push('/admin');
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setError('登录过程中发生错误,请重试');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Container size={420} my={40}>
|
||||
<Center>
|
||||
<Stack align="center" gap="md">
|
||||
<IconLock size={48} color="var(--mantine-color-blue-6)" />
|
||||
<Title order={2} ta="center">
|
||||
WindFonts 管理后台
|
||||
</Title>
|
||||
<Text c="dimmed" size="sm" ta="center">
|
||||
请输入管理员凭据以访问后台管理系统
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
|
||||
<Paper withBorder shadow="md" p={30} mt={30} radius="md">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Stack gap="md">
|
||||
{error && (
|
||||
<Alert
|
||||
icon={<IconAlertCircle size={16} />}
|
||||
color="red"
|
||||
variant="light"
|
||||
>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<TextInput
|
||||
label="用户名"
|
||||
placeholder="请输入用户名"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.currentTarget.value)}
|
||||
required
|
||||
disabled={loading}
|
||||
/>
|
||||
|
||||
<PasswordInput
|
||||
label="密码"
|
||||
placeholder="请输入密码"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.currentTarget.value)}
|
||||
required
|
||||
disabled={loading}
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
fullWidth
|
||||
loading={loading}
|
||||
mt="md"
|
||||
>
|
||||
登录
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
</Paper>
|
||||
</Container>
|
||||
);
|
||||
}
|
47
middleware.ts
Normal file
47
middleware.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (C) 2024 WindFonts Project
|
||||
*
|
||||
* This file is part of WindFonts.
|
||||
*
|
||||
* WindFonts is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* WindFonts is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with WindFonts. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { auth } from '@/lib/auth';
|
||||
|
||||
export default auth((req: NextRequest & { auth: any }) => {
|
||||
const { pathname } = req.nextUrl;
|
||||
|
||||
// 保护 /admin 路由
|
||||
if (pathname.startsWith('/admin')) {
|
||||
if (!req.auth) {
|
||||
// 未登录,重定向到登录页面
|
||||
const loginUrl = new URL('/login', req.url);
|
||||
loginUrl.searchParams.set('callbackUrl', pathname);
|
||||
return NextResponse.redirect(loginUrl);
|
||||
}
|
||||
|
||||
// 检查用户角色
|
||||
if (req.auth.user?.role !== 'admin') {
|
||||
// 非管理员,重定向到首页
|
||||
return NextResponse.redirect(new URL('/', req.url));
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.next();
|
||||
});
|
||||
|
||||
export const config = {
|
||||
matcher: ['/admin/:path*'],
|
||||
};
|
|
@ -39,6 +39,7 @@
|
|||
"lodash-es": "^4.17.21",
|
||||
"modern-screenshot": "^4.4.39",
|
||||
"next": "14.2.4",
|
||||
"next-auth": "^5.0.0-beta.29",
|
||||
"next-compose-plugins": "^2.2.1",
|
||||
"next-contentlayer2": "^0.5.0",
|
||||
"pg": "^8.16.3",
|
||||
|
|
|
@ -1,12 +1,31 @@
|
|||
'use client';
|
||||
|
||||
import { NavLink } from '@mantine/core';
|
||||
import { NavLink, Stack, Text, Button, Divider, Group } from '@mantine/core';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useSession, signOut } from 'next-auth/react';
|
||||
import { IconLogout, IconUser } from '@tabler/icons-react';
|
||||
|
||||
export default function Sidebar() {
|
||||
const pathname = usePathname();
|
||||
const { data: session } = useSession();
|
||||
|
||||
const handleSignOut = async () => {
|
||||
await signOut({ callbackUrl: '/' });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack gap="md">
|
||||
{/* 用户信息 */}
|
||||
<Group gap="xs">
|
||||
<IconUser size={16} />
|
||||
<Text size="sm" fw={500}>
|
||||
{session?.user?.name || '管理员'}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* 导航菜单 */}
|
||||
<NavLink
|
||||
href="/admin/manage"
|
||||
label="字体管理"
|
||||
|
@ -17,6 +36,19 @@ export default function Sidebar() {
|
|||
label="字体上传"
|
||||
active={pathname === '/admin/upload'}
|
||||
/>
|
||||
</>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* 登出按钮 */}
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
size="sm"
|
||||
leftSection={<IconLogout size={16} />}
|
||||
onClick={handleSignOut}
|
||||
>
|
||||
退出登录
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
|
35
src/components/providers/SessionProvider.tsx
Normal file
35
src/components/providers/SessionProvider.tsx
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (C) 2024 WindFonts Project
|
||||
*
|
||||
* This file is part of WindFonts.
|
||||
*
|
||||
* WindFonts is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* WindFonts is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with WindFonts. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
'use client';
|
||||
|
||||
import { SessionProvider as NextAuthSessionProvider } from 'next-auth/react';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
interface SessionProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function SessionProvider({ children }: SessionProviderProps) {
|
||||
return (
|
||||
<NextAuthSessionProvider>
|
||||
{children}
|
||||
</NextAuthSessionProvider>
|
||||
);
|
||||
}
|
75
src/lib/auth.ts
Normal file
75
src/lib/auth.ts
Normal file
|
@ -0,0 +1,75 @@
|
|||
import NextAuth from 'next-auth';
|
||||
import CredentialsProvider from 'next-auth/providers/credentials';
|
||||
|
||||
declare module 'next-auth' {
|
||||
interface User {
|
||||
role: string;
|
||||
}
|
||||
|
||||
interface Session {
|
||||
user: {
|
||||
id: string;
|
||||
name?: string | null;
|
||||
email?: string | null;
|
||||
role: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
declare module '@auth/core/jwt' {
|
||||
interface JWT {
|
||||
role: string;
|
||||
}
|
||||
}
|
||||
|
||||
const config = {
|
||||
providers: [
|
||||
CredentialsProvider({
|
||||
name: 'credentials',
|
||||
credentials: {
|
||||
username: { label: 'Username', type: 'text' },
|
||||
password: { label: 'Password', type: 'password' },
|
||||
},
|
||||
async authorize(credentials) {
|
||||
if (!credentials?.username || !credentials?.password) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 验证管理员账号
|
||||
if (
|
||||
credentials.username === process.env.ADMIN_USERNAME &&
|
||||
credentials.password === process.env.ADMIN_PASSWORD
|
||||
) {
|
||||
return {
|
||||
id: '1',
|
||||
name: 'Administrator',
|
||||
email: process.env.ADMIN_EMAIL || 'admin@windfonts.com',
|
||||
role: 'admin',
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
}),
|
||||
],
|
||||
session: {
|
||||
strategy: 'jwt' as const,
|
||||
},
|
||||
callbacks: {
|
||||
jwt: async ({ token, user }: { token: any; user: any }) => {
|
||||
if (user) {
|
||||
token.role = user.role;
|
||||
}
|
||||
return token;
|
||||
},
|
||||
session: async ({ session, token }: { session: any; token: any }) => {
|
||||
session.user.role = token.role as string;
|
||||
return session;
|
||||
},
|
||||
},
|
||||
pages: {
|
||||
signIn: '/login',
|
||||
},
|
||||
};
|
||||
|
||||
export const { handlers, auth, signIn, signOut } = NextAuth(config);
|
84
yarn.lock
84
yarn.lock
|
@ -219,6 +219,30 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@auth/core@npm:0.40.0":
|
||||
version: 0.40.0
|
||||
resolution: "@auth/core@npm:0.40.0"
|
||||
dependencies:
|
||||
"@panva/hkdf": "npm:^1.2.1"
|
||||
jose: "npm:^6.0.6"
|
||||
oauth4webapi: "npm:^3.3.0"
|
||||
preact: "npm:10.24.3"
|
||||
preact-render-to-string: "npm:6.5.11"
|
||||
peerDependencies:
|
||||
"@simplewebauthn/browser": ^9.0.1
|
||||
"@simplewebauthn/server": ^9.0.2
|
||||
nodemailer: ^6.8.0
|
||||
peerDependenciesMeta:
|
||||
"@simplewebauthn/browser":
|
||||
optional: true
|
||||
"@simplewebauthn/server":
|
||||
optional: true
|
||||
nodemailer:
|
||||
optional: true
|
||||
checksum: 10c0/25cd12f22611eedc21c17dc1908fa9428ae5f0e32eb32c1ab009642276c37099cce58f49ffbb7f8e8d6d6488d5101a24fb9808ec662eee5aca19d520750acaa3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/code-frame@npm:^7.27.1":
|
||||
version: 7.27.1
|
||||
resolution: "@babel/code-frame@npm:7.27.1"
|
||||
|
@ -2266,6 +2290,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@panva/hkdf@npm:^1.2.1":
|
||||
version: 1.2.1
|
||||
resolution: "@panva/hkdf@npm:1.2.1"
|
||||
checksum: 10c0/1fabdec9bd2c19b8e88a3fa6fd0c25e25823c5000d9efdf4b6dfe32e9f370f8b9603cf776d120d160bec15fba17e079974cc34f0f52cebb24602cd832dfde19c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@parcel/watcher-android-arm64@npm:2.5.1":
|
||||
version: 2.5.1
|
||||
resolution: "@parcel/watcher-android-arm64@npm:2.5.1"
|
||||
|
@ -7272,6 +7303,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jose@npm:^6.0.6":
|
||||
version: 6.1.0
|
||||
resolution: "jose@npm:6.1.0"
|
||||
checksum: 10c0/f4518579e907317e144facd15c7627acd06097bbea17735097437217498aa419564c039dd4020f6af5f2d024a7cee6b7be4648ccbbdc238aedb80a47c061217d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"js-base64@npm:^2.5.2":
|
||||
version: 2.6.4
|
||||
resolution: "js-base64@npm:2.6.4"
|
||||
|
@ -8877,6 +8915,28 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"next-auth@npm:^5.0.0-beta.29":
|
||||
version: 5.0.0-beta.29
|
||||
resolution: "next-auth@npm:5.0.0-beta.29"
|
||||
dependencies:
|
||||
"@auth/core": "npm:0.40.0"
|
||||
peerDependencies:
|
||||
"@simplewebauthn/browser": ^9.0.1
|
||||
"@simplewebauthn/server": ^9.0.2
|
||||
next: ^14.0.0-0 || ^15.0.0-0
|
||||
nodemailer: ^6.6.5
|
||||
react: ^18.2.0 || ^19.0.0-0
|
||||
peerDependenciesMeta:
|
||||
"@simplewebauthn/browser":
|
||||
optional: true
|
||||
"@simplewebauthn/server":
|
||||
optional: true
|
||||
nodemailer:
|
||||
optional: true
|
||||
checksum: 10c0/2c6bada9a5f28a9a172d3ad295bfb05b648a4fced01f9988154df1ebca712cf460fb49173ada4c26de4c7ab180256f40ac19d16e2147c1c68f2a7475ab5d5ea8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"next-compose-plugins@npm:^2.2.1":
|
||||
version: 2.2.1
|
||||
resolution: "next-compose-plugins@npm:2.2.1"
|
||||
|
@ -8964,6 +9024,7 @@ __metadata:
|
|||
lodash-es: "npm:^4.17.21"
|
||||
modern-screenshot: "npm:^4.4.39"
|
||||
next: "npm:14.2.4"
|
||||
next-auth: "npm:^5.0.0-beta.29"
|
||||
next-compose-plugins: "npm:^2.2.1"
|
||||
next-contentlayer2: "npm:^0.5.0"
|
||||
node-loader: "npm:^2.1.0"
|
||||
|
@ -9188,6 +9249,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"oauth4webapi@npm:^3.3.0":
|
||||
version: 3.8.1
|
||||
resolution: "oauth4webapi@npm:3.8.1"
|
||||
checksum: 10c0/2dad6d39d4830efe68d542e8e131fd5b15d5a864f96ad7189263da9763cad0e22481af72e50d64d58ab62887ba43488bff5d33952426c5d197089cc7c59b2a45
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"object-assign@npm:^4.0.1, object-assign@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "object-assign@npm:4.1.1"
|
||||
|
@ -9892,6 +9960,22 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"preact-render-to-string@npm:6.5.11":
|
||||
version: 6.5.11
|
||||
resolution: "preact-render-to-string@npm:6.5.11"
|
||||
peerDependencies:
|
||||
preact: ">=10"
|
||||
checksum: 10c0/a68b704c1343756022fb41322154b45e39f6021b940cbc97e86c292627d017d019700339a17d67b42c308b14fc5323fc120c798f785d6a3e993c8f273610bfe2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"preact@npm:10.24.3":
|
||||
version: 10.24.3
|
||||
resolution: "preact@npm:10.24.3"
|
||||
checksum: 10c0/c863df6d7be6a660480189762d8a8f2d4148733fc2bb9efbd9d2fd27315d2c7ede850a16077d716c91666c915c0349bd3c9699733e4f08457226a0519f408761
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"prelude-ls@npm:^1.2.1":
|
||||
version: 1.2.1
|
||||
resolution: "prelude-ls@npm:1.2.1"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue