diff --git a/src/app/(auth)/components/index.tsx b/src/app/(auth)/components/index.tsx index 3ad86a2f..d042b330 100644 --- a/src/app/(auth)/components/index.tsx +++ b/src/app/(auth)/components/index.tsx @@ -1,26 +1,80 @@ "use client" -import { useState } from "react" +import { startTransition, useEffect, useRef, useState } from "react" import styles from "./auth.module.css" import Link from "../../components/link" -import { signIn } from "next-auth/react" +import { getSession, signIn } from "next-auth/react" import Input from "@components/input" import Button from "@components/button" -import Note from "@components/note" import { GitHub } from "react-feather" +import { useToasts } from "@components/toasts" +import { useRouter, useSearchParams } from "next/navigation" +import Note from "@components/note" const Auth = ({ page, - requiresServerPassword + requiresServerPassword, + isGithubEnabled }: { page: "signup" | "signin" requiresServerPassword?: boolean + isGithubEnabled?: boolean }) => { const [serverPassword, setServerPassword] = useState("") - const [errorMsg, setErrorMsg] = useState("") + const { setToast } = useToasts() const signingIn = page === "signin" + const router = useRouter() const signText = signingIn ? "In" : "Up" const [username, setUsername] = useState("") const [password, setPassword] = useState("") + const queryParams = useSearchParams() + + useEffect(() => { + if (queryParams.get("error")) { + setToast({ + message: queryParams.get("error") as string, + type: "error" + }) + } + }, [queryParams, setToast]) + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault() + + const res = await signIn("credentials", { + username, + password, + registration_password: serverPassword, + redirect: false, + // callbackUrl: "/signin", + signingIn: signingIn + }) + if (res?.error) { + setToast({ + type: "error", + message: res.error + }) + } else { + console.log("res", res) + startTransition(() => { + router.push("/new") + router.refresh() + }) + } + } + + const handleChangeUsername = (event: React.ChangeEvent) => { + setUsername(event.target.value) + } + + const handleChangePassword = (event: React.ChangeEvent) => { + setPassword(event.target.value) + } + + const handleChangeServerPassword = ( + event: React.ChangeEvent + ) => { + setServerPassword(event.target.value) + } return (
@@ -28,16 +82,37 @@ const Auth = ({

Sign {signText}

- {/*
*/} - +
+ {requiresServerPassword ? ( + <> + {" "} + + The server administrator has set a password for this server. + + + setServerPassword(event.currentTarget.value) + } + placeholder="Server Password" + required={true} + width="100%" + aria-label="Server Password" + /> +
+ + ) : null} + setUsername(event.currentTarget.value)} + onChange={handleChangeUsername} placeholder="Username" - required + required={true} minLength={3} width="100%" aria-label="Username" @@ -46,59 +121,37 @@ const Auth = ({ type="password" id="password" value={password} - onChange={(event) => setPassword(event.currentTarget.value)} + onChange={handleChangePassword} placeholder="Password" - required + required={true} minLength={6} width="100%" aria-label="Password" /> - {requiresServerPassword && ( - - setServerPassword(event.currentTarget.value) - } - placeholder="Server Password" - required - width="100%" - aria-label="Server Password" - /> - )} - -
- + {isGithubEnabled ?
: null} + {isGithubEnabled ? ( + + ) : null}
{signingIn ? ( @@ -117,7 +170,6 @@ const Auth = ({

)}
- {errorMsg && {errorMsg}}
diff --git a/src/app/(auth)/signin/page.tsx b/src/app/(auth)/signin/page.tsx index b18f16b8..6e3fae90 100644 --- a/src/app/(auth)/signin/page.tsx +++ b/src/app/(auth)/signin/page.tsx @@ -1,5 +1,10 @@ +import config from "@lib/config" import Auth from "../components" -export default function SignInPage() { - return +export function isGithubEnabled() { + return config.github_client_id.length && config.github_client_secret.length ? true : false +} + +export default function SignInPage() { + return } diff --git a/src/app/(auth)/signup/page.tsx b/src/app/(auth)/signup/page.tsx index b305301c..5cd1962f 100644 --- a/src/app/(auth)/signup/page.tsx +++ b/src/app/(auth)/signup/page.tsx @@ -1,5 +1,6 @@ import Auth from "../components" import { getRequiresPasscode } from "pages/api/auth/requires-passcode" +import { isGithubEnabled } from "../signin/page" const getPasscode = async () => { return await getRequiresPasscode() @@ -7,5 +8,5 @@ const getPasscode = async () => { export default async function SignUpPage() { const requiresPasscode = await getPasscode() - return + return } diff --git a/src/app/(posts)/new/components/new.tsx b/src/app/(posts)/new/components/new.tsx index 4318d24f..2437379b 100644 --- a/src/app/(posts)/new/components/new.tsx +++ b/src/app/(posts)/new/components/new.tsx @@ -187,7 +187,7 @@ const Post = ({ ) if (session.status === "unauthenticated") { - router.push("/login") + router.push("/signin") return null } diff --git a/src/app/admin/admin.module.css b/src/app/admin/admin.module.css deleted file mode 100644 index 92e1ebc8..00000000 --- a/src/app/admin/admin.module.css +++ /dev/null @@ -1,13 +0,0 @@ -.table { - width: 100%; - display: block; - white-space: nowrap; -} - -.table thead th { - font-weight: bold; -} - -.table .id { - width: 10ch; -} diff --git a/src/app/admin/components/table.module.css b/src/app/admin/components/table.module.css new file mode 100644 index 00000000..03d85ffa --- /dev/null +++ b/src/app/admin/components/table.module.css @@ -0,0 +1,17 @@ +.table { + width: 100%; + display: block; + white-space: nowrap; +} + +.table thead th { + font-weight: bold; +} + +.id { + width: 130px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: block; +} diff --git a/src/app/admin/components/tables.tsx b/src/app/admin/components/tables.tsx new file mode 100644 index 00000000..385b2331 --- /dev/null +++ b/src/app/admin/components/tables.tsx @@ -0,0 +1,138 @@ +"use client" + +import Button from "@components/button" +import ButtonDropdown from "@components/button-dropdown" +import { Spinner } from "@components/spinner" +import { useToasts } from "@components/toasts" +import { Post, User } from "@lib/server/prisma" +import { useState } from "react" +import styles from "./table.module.css" + +export function UserTable({ + users: initialUsers +}: { + users?: { + createdAt: string + posts?: Post[] + id: string + email: string | null + role: string | null + displayName: string | null + + }[] +}) { + const { setToast } = useToasts() + const [users, setUsers] = useState(initialUsers) + + const deleteUser = async (id: string) => { + try { + const res = await fetch("/api/admin?action=delete-user", { + method: "DELETE", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + userId: id + }) + }) + + if (res.status === 200) { + setToast({ + message: "User deleted", + type: "success" + }) + setUsers(users?.filter((user) => user.id !== id)) + } + } catch (err) { + console.error(err) + setToast({ + message: "Error deleting user", + type: "error" + }) + } + } + + return ( + + + + + + + + + + + + {!users ? ( + + + + ) : null} + {users?.map((user) => ( + + + + + + + + ))} + +
NameEmailRoleUser IDActions
+ +
{user.displayName ? user.displayName : "no name"}{user.email}{user.role} + {user.id} + + +
+ ) +} + +export function PostTable({ + posts +}: { + posts?: { + createdAt: string + id: string + author?: User | null + title: string + visibility: string + }[] +}) { + return ( + + + + + + + + + + + + {!posts ? ( + + + + ) : null} + {posts?.map((post) => ( + + + + + + + + ))} + +
TitleAuthorCreatedVisibilityPost ID
+ +
+ + {post.title} + + {"author" in post ? post.author?.name : "no author"}{new Date(post.createdAt).toLocaleDateString()}{post.visibility}{post.id}
+ ) +} diff --git a/src/app/admin/loading.tsx b/src/app/admin/loading.tsx index 2d516c49..98042fcf 100644 --- a/src/app/admin/loading.tsx +++ b/src/app/admin/loading.tsx @@ -1,4 +1,4 @@ -import { PostTable, UserTable } from "./page" +import { PostTable, UserTable } from "./components/tables" export default function AdminLoading() { return ( diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index a89a1412..a0cefe4d 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -1,100 +1,55 @@ -import { getAllPosts, getAllUsers } from "@lib/server/prisma" import { Spinner } from "@components/spinner" -import styles from "./admin.module.css" - -export function UserTable({ - users -}: { - users?: Awaited> -}) { - return ( - - - - - - - - - - - {users?.map((user) => ( - - - - - - - ))} - {!users && ( - - - - )} - -
NameEmailRoleUser ID
{user.displayName ? user.displayName : "no name"}{user.email}{user.role}{user.id}
- -
- ) -} - -export function PostTable({ - posts -}: { - posts?: Awaited> -}) { - return ( - - - - - - - - - - - - {posts?.map((post) => ( - - - - - - - - ))} - {!posts && ( - - - - )} - -
TitleAuthorCreatedVisibilityPost ID
- - {post.title} - - {"author" in post ? post.author?.name : "no author"}{post.createdAt.toLocaleDateString()}{post.visibility}{post.id}
- -
- ) -} +import { getAllPosts, getAllUsers } from "@lib/server/prisma" +import { PostTable, UserTable } from "./components/tables" export default async function AdminPage() { - const usersPromise = getAllUsers() + const usersPromise = getAllUsers({ + select: { + id: true, + name: true, + createdAt: true + } + }) const postsPromise = getAllPosts({ - withAuthor: true + select: { + id: true, + title: true, + createdAt: true, + updatedAt: true, + author: { + select: { + name: true + } + } + } }) const [users, posts] = await Promise.all([usersPromise, postsPromise]) + const serializedPosts = posts.map((post) => { + return { + ...post, + createdAt: post.createdAt.toISOString(), + updatedAt: post.updatedAt.toISOString(), + expiresAt: post.expiresAt?.toISOString(), + deletedAt: post.deletedAt?.toISOString() + } + }) + + const serializedUsers = users.map((user) => { + return { + ...user, + createdAt: user.createdAt.toISOString() + } + }) + return (

Admin

Users

- +

Posts

- +
) } diff --git a/src/app/components/header/index.tsx b/src/app/components/header/index.tsx index 73629f4a..fb3d4e33 100644 --- a/src/app/components/header/index.tsx +++ b/src/app/components/header/index.tsx @@ -34,6 +34,7 @@ type Tab = { const Header = () => { const session = useSession() + console.log("session", session) const isSignedIn = session?.status === "authenticated" const isAdmin = session?.data?.user?.role === "admin" const isLoading = session?.status === "loading" diff --git a/src/app/components/input/index.tsx b/src/app/components/input/index.tsx index 4f53312b..be48bdfe 100644 --- a/src/app/components/input/index.tsx +++ b/src/app/components/input/index.tsx @@ -40,7 +40,7 @@ type InputProps = Omit & ) // eslint-disable-next-line react/display-name const Input = React.forwardRef( - ({ label, className, width, height, labelClassName, ...props }, ref) => { + ({ label, className, required, width, height, labelClassName, ...props }, ref) => { return (
{ export default async function Page() { const { content, rendered, title } = await getWelcomeData() const getPostsPromise = getAllPosts({ - where: { visibility: "public" }, - include: { - files: true + select: { + id: true, + title: true, + createdAt: true, + author: { + select: { + name: true + } + } + }, + where: { + deletedAt: null, + expiresAt: { + gt: new Date() + } + }, + orderBy: { + createdAt: "desc" } }) diff --git a/src/lib/config.ts b/src/lib/config.ts index 7a2cf263..1aaba368 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -1,4 +1,3 @@ - type Config = { is_production: boolean enable_admin: boolean @@ -9,6 +8,7 @@ type Config = { github_client_id: string github_client_secret: string nextauth_secret: string + credential_auth: boolean } type EnvironmentValue = string | undefined @@ -77,6 +77,9 @@ export const config = (env: Environment): Config => { github_client_id: env.GITHUB_CLIENT_ID ?? "", github_client_secret: env.GITHUB_CLIENT_SECRET ?? "", nextauth_secret: throwIfUndefined("NEXTAUTH_SECRET"), + credential_auth: stringToBoolean( + developmentDefault("CREDENTIAL_AUTH", "true") + ) } return config } diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts index a760102f..1921dded 100644 --- a/src/lib/server/auth.ts +++ b/src/lib/server/auth.ts @@ -5,83 +5,140 @@ import CredentialsProvider from "next-auth/providers/credentials" import { prisma } from "@lib/server/prisma" import config from "@lib/config" import * as crypto from "crypto" +import { Provider } from "next-auth/providers" -const providers: NextAuthOptions["providers"] = [ - GitHubProvider({ - clientId: config.github_client_id, - clientSecret: config.github_client_secret - }), - CredentialsProvider({ - name: "Credentials", - credentials: { - username: { label: "Username", type: "text", placeholder: "jsmith" }, - password: { label: "Password", type: "password" } +const credentialsOptions = () => { + const options: Record = { + username: { + label: "Username", + required: true, + type: "text" }, - async authorize(credentials) { - if (!credentials) { - return null - } + password: { + label: "Password", + required: true, + type: "password" + } + } - const user = await prisma.user.findUnique({ - where: { - username: credentials.username - }, - select: { - id: true, - username: true, - displayName: true, - role: true, - password: true + if (config.registration_password) { + options["registration_password"] = { + label: "Server Password", + type: "password", + optional: true + } + } + + return options +} +const providers = () => { + const providers = [] + + if (config.github_client_id && config.github_client_secret) { + providers.push( + GitHubProvider({ + clientId: config.github_client_id, + clientSecret: config.github_client_secret + }) + ) + } + + if (config.credential_auth) { + providers.push( + CredentialsProvider({ + name: "Drift", + credentials: credentialsOptions(), + async authorize(credentials) { + if (!credentials || !credentials.username || !credentials.password) { + throw new Error("Missing credentials") + } + + if (credentials.username.length < 3) { + throw new Error("Username must be at least 3 characters") + } + + if (credentials.password.length < 3) { + throw new Error("Password must be at least 3 characters") + } + + const user = await prisma.user.findUnique({ + where: { + username: credentials.username + }, + select: { + id: true, + username: true, + displayName: true, + role: true, + password: true + } + }) + + const hashedPassword = crypto + .createHash("sha256") + .update(credentials.password + config.nextauth_secret) + .digest("hex") + + if (credentials.signingIn === "true") { + if ( + user?.password && + crypto.timingSafeEqual( + Buffer.from(user.password), + Buffer.from(hashedPassword) + ) + ) { + return user + } else { + throw new Error("Incorrect username or password") + } + } else { + if (config.registration_password) { + if (!credentials.registration_password) { + throw new Error("Missing registration password") + } + + if ( + credentials.registration_password !== + config.registration_password + ) { + throw new Error("Incorrect registration password") + } + } + + if (user) { + throw new Error("Username already taken") + } + + const newUser = await prisma.user.create({ + data: { + username: credentials.username, + displayName: credentials.username, + role: "user", + password: hashedPassword, + name: credentials.username + } + }) + + return newUser + } } }) + ) + } - const hashedPassword = crypto - .createHash("sha256") - .update - (credentials - .password - + config.nextauth_secret) - .digest("hex") - - if (!user) { - const newUser = await prisma.user.create({ - data: { - username: credentials.username, - displayName: credentials.username, - role: "user", - password: hashedPassword, - name: credentials.username, - } - }) - - return newUser - } else if ( - user.password && - crypto.timingSafeEqual( - Buffer.from(user.password), - Buffer.from(hashedPassword) - ) - ) { - return user - } - - return null - } - }) -] + return providers +} export const authOptions: NextAuthOptions = { - // see https://github.com/prisma/prisma/issues/16117 / https://github.com/shadcn/taxonomy - adapter: PrismaAdapter(prisma as any), + adapter: PrismaAdapter(prisma), session: { strategy: "jwt" }, pages: { - signIn: "/signin" - // TODO - // error: "/auth/error", + signIn: "/signin", + error: "/signin" }, - providers, + providers: providers(), callbacks: { async session({ token, session }) { if (token) { @@ -94,6 +151,7 @@ export const authOptions: NextAuthOptions = { return session }, + async jwt({ token, user }) { const dbUser = await prisma.user.findFirst({ where: { diff --git a/src/lib/server/prisma.ts b/src/lib/server/prisma.ts index 99108b8f..87e373fd 100644 --- a/src/lib/server/prisma.ts +++ b/src/lib/server/prisma.ts @@ -57,7 +57,9 @@ const postWithFilesAndAuthor = Prisma.validator()({ export type ServerPostWithFiles = Prisma.PostGetPayload export type PostWithAuthor = Prisma.PostGetPayload -export type ServerPostWithFilesAndAuthor = Prisma.PostGetPayload +export type ServerPostWithFilesAndAuthor = Prisma.PostGetPayload< + typeof postWithFilesAndAuthor +> export type PostWithFiles = Omit & { files: (Omit & { @@ -70,7 +72,10 @@ export type PostWithFilesAndAuthor = Omit< ServerPostWithFilesAndAuthor, "files" > & { - files: (Omit & { + files: (Omit< + ServerPostWithFilesAndAuthor["files"][number], + "content" | "html" + > & { content: string html: string })[] @@ -201,40 +206,25 @@ export const getPostById = async ( return post } -export const getAllPosts = async ({ - withFiles = false, - withAuthor = false, - take = 100, - ...rest -}: { - withFiles?: boolean - withAuthor?: boolean -} & Prisma.PostFindManyArgs = {}): Promise< - Post[] | ServerPostWithFiles[] | ServerPostWithFilesAndAuthor[] -> => { - const posts = await prisma.post.findMany({ - include: { - files: withFiles, - author: withAuthor - }, - // TODO: optimize which to grab - take, - ...rest - }) - - return posts as typeof withFiles extends true - ? typeof withAuthor extends true - ? PostWithFilesAndAuthor[] - : PostWithFiles[] - : Post[] +export const getAllPosts = async ( + options?: Prisma.PostFindManyArgs +): Promise => { + const posts = await prisma.post.findMany(options) + return posts } -export type UserWithPosts = User & { - posts: Post[] -} +export const userWithPosts = Prisma.validator()({ + include: { + posts: true + } +}) -export const getAllUsers = async () => { - const users = await prisma.user.findMany({ +export type UserWithPosts = Prisma.UserGetPayload + +export const getAllUsers = async ( + options?: Prisma.UserFindManyArgs +): Promise => { + const users = (await prisma.user.findMany({ select: { id: true, email: true, @@ -242,8 +232,9 @@ export const getAllUsers = async () => { displayName: true, posts: true, createdAt: true - } - }) + }, + ...options + })) as User[] | UserWithPosts[] return users } @@ -265,7 +256,7 @@ export const searchPosts = async ( OR: [ { title: { - search: query, + search: query }, authorId: userId, visibility: publicOnly ? "public" : undefined @@ -275,7 +266,7 @@ export const searchPosts = async ( some: { content: { in: [Buffer.from(query)] - }, + } } }, visibility: publicOnly ? "public" : undefined diff --git a/src/next.config.mjs b/src/next.config.mjs index f08930b3..8612c88f 100644 --- a/src/next.config.mjs +++ b/src/next.config.mjs @@ -6,10 +6,10 @@ const nextConfig = { experimental: { // esmExternals: true, appDir: true, - serverComponentsExternalPackages: ['prisma'], + serverComponentsExternalPackages: ["prisma", "@prisma/client"], }, output: "standalone", - async rewrites() { + rewrites() { return [ { source: "/file/raw/:id", @@ -21,7 +21,6 @@ const nextConfig = { } ] } - } export default bundleAnalyzer({ enabled: process.env.ANALYZE === "true" })( diff --git a/src/package.json b/src/package.json index abf54efc..e944965e 100644 --- a/src/package.json +++ b/src/package.json @@ -28,7 +28,7 @@ "jest": "^29.3.1", "lodash.debounce": "^4.0.8", "next": "13.0.8-canary.0", - "next-auth": "^4.18.4", + "next-auth": "^4.18.6", "prisma": "^4.7.1", "react": "18.2.0", "react-datepicker": "4.8.0", diff --git a/src/pages/api/admin/index.ts b/src/pages/api/admin/index.ts index 812f4c41..105e28fd 100644 --- a/src/pages/api/admin/index.ts +++ b/src/pages/api/admin/index.ts @@ -3,6 +3,7 @@ import { parseQueryParam } from "@lib/server/parse-query-param" import { NextApiRequest, NextApiResponse } from "next" import { prisma } from "lib/server/prisma" import { getSession } from "next-auth/react" +import { deleteUser } from "../user/[id]" const actions = [ "user", @@ -94,11 +95,9 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { return res.status(400).json({ error: "Invalid request" }) } - const user = await prisma.user.delete({ - where: { id: userId } - }) + await deleteUser(userId) - return res.status(200).json(user) + return res.status(200).send("User deleted") case "delete-post": const { postId } = req.body if (!postId) { diff --git a/src/pages/api/user/[id].ts b/src/pages/api/user/[id].ts index b4229fac..421d094d 100644 --- a/src/pages/api/user/[id].ts +++ b/src/pages/api/user/[id].ts @@ -42,9 +42,42 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { }) case "GET": return res.json(currUser) + case "DELETE": + if (currUser?.role !== "admin" && currUser?.id !== id) { + return res.status(403).json({ message: "Unauthorized" }) + } + + await deleteUser(id) + default: return res.status(405).json({ message: "Method not allowed" }) } } -export default withMethods(["GET", "PUT"], handler) +export default withMethods(["GET", "PUT", "DELETE"], handler) + +// valid jsdoc +/** + * @description Deletes a user and all of their posts, files, and accounts + * @warning This function does not perform any authorization checks + */ +export async function deleteUser(id: string | undefined) { + + // first delete all of the user's posts + await prisma.post.deleteMany({ + where: { + authorId: id + } + }) + + await prisma.user.delete({ + where: { + id + }, + include: { + posts: true, + accounts: true, + sessions: true + } + }) +} diff --git a/src/pnpm-lock.yaml b/src/pnpm-lock.yaml index 20ee055b..24a800a7 100644 --- a/src/pnpm-lock.yaml +++ b/src/pnpm-lock.yaml @@ -28,7 +28,7 @@ specifiers: jest: ^29.3.1 lodash.debounce: ^4.0.8 next: 13.0.8-canary.0 - next-auth: ^4.18.4 + next-auth: ^4.18.6 next-unused: 0.0.6 prettier: 2.6.2 prisma: ^4.7.1 @@ -46,7 +46,7 @@ specifiers: typescript-plugin-css-modules: 3.4.0 dependencies: - '@next-auth/prisma-adapter': 1.0.5_4eojhct6t46nl4awizrjr4dkya + '@next-auth/prisma-adapter': 1.0.5_64qbzg5ec56bux2misz3l4u6g4 '@next/eslint-plugin-next': 13.0.7-canary.4 '@prisma/client': 4.7.1_prisma@4.7.1 '@radix-ui/react-dialog': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u @@ -61,7 +61,7 @@ dependencies: jest: 29.3.1_@types+node@17.0.23 lodash.debounce: 4.0.8 next: 13.0.8-canary.0_biqbaboplfbrettd7655fr4n2y - next-auth: 4.18.4_rhfownvlqkszea7w3lnpwl7bzy + next-auth: 4.18.6_rhfownvlqkszea7w3lnpwl7bzy prisma: 4.7.1 react: 18.2.0 react-datepicker: 4.8.0_biqbaboplfbrettd7655fr4n2y @@ -773,14 +773,14 @@ packages: '@jridgewell/sourcemap-codec': 1.4.14 dev: false - /@next-auth/prisma-adapter/1.0.5_4eojhct6t46nl4awizrjr4dkya: + /@next-auth/prisma-adapter/1.0.5_64qbzg5ec56bux2misz3l4u6g4: resolution: {integrity: sha512-VqMS11IxPXrPGXw6Oul6jcyS/n8GLOWzRMrPr3EMdtD6eOalM6zz05j08PcNiis8QzkfuYnCv49OvufTuaEwYQ==} peerDependencies: '@prisma/client': '>=2.26.0 || >=3' next-auth: ^4 dependencies: '@prisma/client': 4.7.1_prisma@4.7.1 - next-auth: 4.18.4_rhfownvlqkszea7w3lnpwl7bzy + next-auth: 4.18.6_rhfownvlqkszea7w3lnpwl7bzy dev: false /@next/bundle-analyzer/13.0.7-canary.4: @@ -5105,8 +5105,8 @@ packages: dev: true optional: true - /next-auth/4.18.4_rhfownvlqkszea7w3lnpwl7bzy: - resolution: {integrity: sha512-tvXOabxv5U/y6ib56XPkOnc/48tYc+xT6GNOLREIme8WVGYHDTc3CGEfe2+0bVCWAm0ax/GYXH0By5NFoaJDww==} + /next-auth/4.18.6_rhfownvlqkszea7w3lnpwl7bzy: + resolution: {integrity: sha512-0TQwbq5X9Jyd1wUVYUoyvHJh4JWXeW9UOcMEl245Er/Y5vsSbyGJHt8M7xjRMzk9mORVMYehoMdERgyiq/jCgA==} engines: {node: ^12.19.0 || ^14.15.0 || ^16.13.0 || ^18.12.0} peerDependencies: next: ^12.2.5 || ^13